770 lines
26 KiB
Diff
770 lines
26 KiB
Diff
From d22ccd84355100e95b4aafa6ed9753236533dfec Mon Sep 17 00:00:00 2001
|
||
From: ganlixiong <ganli2012@gmail.com>
|
||
Date: Tue, 24 Sep 2024 15:18:52 +0800
|
||
Subject: [PATCH 02/20] update docs.
|
||
|
||
Update README and add Details.md for detailed usage of libkperf.
|
||
---
|
||
README.en.md | 178 +++-----------------------------
|
||
README.md | 179 ++++----------------------------
|
||
docs/Details.md | 264 ++++++++++++++++++++++++++++++++++++++++++++++++
|
||
3 files changed, 298 insertions(+), 323 deletions(-)
|
||
create mode 100644 docs/Details.md
|
||
|
||
diff --git a/README.en.md b/README.en.md
|
||
index ff85b3a..40fbc26 100644
|
||
--- a/README.en.md
|
||
+++ b/README.en.md
|
||
@@ -1,58 +1,36 @@
|
||
# libkperf
|
||
|
||
#### Description
|
||
-Implement a low overhead pmu collection library, providing abstract interfaces for counting, sampling and symbol resolve.
|
||
+libkperf is a lightweight performance collection library on linux, that enables developers to perform performance collection in an API fashion. libkperf provides performance data in memory and allows develops to process data directly, reducing overhead of writing and reading perf.data.
|
||
|
||
-#### Software Architecture
|
||
-This repo includes two modules: pmu collections and symbol resolve.
|
||
-
|
||
-Pmu collection module is developed on syscall perf_event_open to enable kernel pmu counting and sampling, using -thread or per-core mode depending on user input.
|
||
-Pmu data packets are read from ring buffer and are parsed to different structure for counting, sampling and spe sampling.
|
||
-For sampling, symbols are resolved according to ips or pc from data packet. Each symbol contains symbol name, address, source file path and line number if possible.
|
||
-
|
||
-Symbol resolve module is developed on elfin-parser, a library for parsing elf and dwarf. The module manages all symbol data in well-designed data structures for fast query.
|
||
-
|
||
-#### Download
|
||
-
|
||
-Git method:
|
||
+#### Build
|
||
|
||
+To build a library with C API:
|
||
```shell
|
||
git clone --recurse-submodules https://gitee.com/openeuler/libkperf.git
|
||
-```
|
||
-If you only use
|
||
-```shell
|
||
-git clone https://gitee.com/openeuler/libkperf.git
|
||
-```
|
||
-Please continue with the execution
|
||
-```shell
|
||
cd libkperf
|
||
-git submodule update --init --recursive
|
||
+bash build.sh install_path=/path/to/install
|
||
```
|
||
|
||
-When unable to use git:
|
||
-
|
||
-1. Download the libkperf compressed file and decompress it.
|
||
-
|
||
-2. Go to the third_party directory of libkperf on Gitee, click on the link(as shown in the example elfin-parser@13e57e2 Click on the submit ID after @), to redirect and download the compressed package of the third-party library. After decompression, place it in the third_party directory of the local libkperf project. (elfin Parser is necessary for installation)
|
||
-
|
||
-#### Installation
|
||
-Run bash script:
|
||
-
|
||
-```sh
|
||
-bash build.sh install_path=/home/libkperf
|
||
+To build a library with debug version:
|
||
+```shell
|
||
+bash build.sh install_path=/path/to/install buildType=debug
|
||
```
|
||
-As mentioned above, the header and library will be installed in the/home/libkperf output directory, and installPath is an optional parameter. If not set, it will be installed in the output directory under libkperf by default.
|
||
|
||
-If you want to add additional python library support, you can install it as follows:
|
||
+To build a python package:
|
||
```shell
|
||
-bash build.sh python=true
|
||
+bash build.sh install_path=/path/to/install python=true
|
||
```
|
||
|
||
-If you need to uninstall the python library after installation, you can run the following command:
|
||
+To uninstall python package:
|
||
```shell
|
||
-python -m pip uninstall -y libkperf
|
||
+python3 -m pip uninstall -y libkperf
|
||
```
|
||
|
||
+#### Documents
|
||
+Refer to ```docs``` directory for detailed docs:
|
||
+- [Detailed usage](./docs/Details.md)
|
||
+
|
||
#### Instructions
|
||
All pmu functions are accomplished by the following interfaces:
|
||
* PmuOpen
|
||
@@ -60,14 +38,12 @@ All pmu functions are accomplished by the following interfaces:
|
||
* PmuEnable
|
||
Start collection.
|
||
* PmuRead
|
||
- Read pmu data and a list is returned.
|
||
+ Read collection data.
|
||
* PmuDisable
|
||
Stop collection.
|
||
* PmuClose
|
||
Close pmu device.
|
||
|
||
-Refer to pmu.h for details of interfaces.
|
||
-
|
||
Here are some examples:
|
||
* Get pmu count for a process.
|
||
```C
|
||
@@ -147,89 +123,16 @@ PmuDataFree(data);
|
||
// Like fd, call PmuClose if pd will not be used.
|
||
PmuClose(pd);
|
||
```
|
||
-* config event group function
|
||
-```C
|
||
-int pidList[1];
|
||
-pidList[0] = pid;
|
||
-unsigned numEvt = 16;
|
||
-char *evtList[numEvt] = {"r3", "r4", "r1", "r14", "r10", "r12", "r5", "r25",
|
||
- "r2", "r26", "r2d", "r17", "r8", "r22", "r24", "r11"};
|
||
-// initialize event list, the same group id is the same event group.
|
||
-// if event grouping is not required, leave the event group_id list blank.
|
||
-// In addition, if group_id is -1, the event group function is forcibly disabled.
|
||
-PmuAttr attr = {0};
|
||
-attr.evtList = evtList;
|
||
-attr.numEvt = numEvt;
|
||
-attr.pidList = pidList;
|
||
-attr.numPid = 1;
|
||
-struct EvttAttr groupId[numEvt] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 13, 13};
|
||
-attr.evtAttr = groupId;
|
||
-// Call PmuOpen and pmu descriptor <pd> is return.
|
||
-// <pd> is an identity for current task.
|
||
-int pd = PmuOpen(COUNTING, &attr);
|
||
-// Start collection.
|
||
-PmuEnable(pd);
|
||
-// Collect for one second.
|
||
-sleep(1);
|
||
-// Stop collection.
|
||
-PmuDisable(pd);
|
||
-PmuData *data = NULL;
|
||
-// Read pmu data. You can also read data before PmuDisable.
|
||
-int len = PmuRead(pd, &data);
|
||
-for (int i = 0; i < len; ++i) {
|
||
- ...
|
||
-}
|
||
-// To free PmuData, call PmuDataFree.
|
||
-PmuDataFree(data);
|
||
-// Like fd, call PmuClose if pd will not be used.
|
||
-PmuClose(pd);
|
||
-```
|
||
-
|
||
-- Counting supports fork thread.
|
||
-```C
|
||
-int pidList[1];
|
||
-pidList[0] = pid;
|
||
-unsigned numEvt = 1;
|
||
-char *evtList[numEvt] = {"cycles"};
|
||
-PmuAttr attr = {0};
|
||
-attr.evtList = evtList;
|
||
-attr.numEvt = numEvt;
|
||
-attr.pidList = pidList;
|
||
-attr.numPid = 1;
|
||
-// In count mode, enable it you can get the new child thread count, default is disabled.
|
||
-attr.includeNewFork = 1;
|
||
-// Call PmuOpen and pmu descriptor <pd> is return.
|
||
-// <pd> is an identity for current task.
|
||
-int pd = PmuOpen(COUNTING, &attr);
|
||
-// Start collection.
|
||
-PmuEnable(pd);
|
||
-// Collect for two second.
|
||
-sleep(2);
|
||
-// Stop collection.
|
||
-PmuDisable(pd);
|
||
-PmuData *data = NULL;
|
||
-// Read pmu data. You can also read data before PmuDisable.
|
||
-int len = PmuRead(pd, &data);
|
||
-for (int i = 0; i < len; ++i) {
|
||
- ...
|
||
-}
|
||
-// To free PmuData, call PmuDataFree.
|
||
-PmuDataFree(data);
|
||
-// Like fd, call PmuClose if pd will not be used.
|
||
-PmuClose(pd);
|
||
-```
|
||
|
||
Python examples:
|
||
```python
|
||
import time
|
||
from collections import defaultdict
|
||
-import subprocess
|
||
|
||
import kperf
|
||
|
||
def Counting():
|
||
evtList = ["r11", "cycles"]
|
||
- evtAttr = [2, 2] # Event group id list corresponding to the event list. the same group id is the same event group.
|
||
pmu_attr = kperf.PmuAttr(evtList=evtList)
|
||
pd = kperf.open(kperf.PmuTaskType.COUNTING, pmu_attr)
|
||
if pd == -1:
|
||
@@ -250,53 +153,4 @@ def Counting():
|
||
|
||
kperf.disable(pd)
|
||
kperf.close(pd)
|
||
-
|
||
-
|
||
-def NewFork():
|
||
- # test_new_fork demo in test_perf, you can find test_new_fork.cpp
|
||
- p=subprocess.Popen(['test_new_fork']);
|
||
- pidList=[p.pid]
|
||
- evtList=["cycles"]
|
||
- pmu_attr = kperf.PmuAttr(evtList=evtList, includeNewFork=True, pidList=pidList)
|
||
- pd = kperf.open(kperf.PmuTaskType.COUNTING, pmu_attr)
|
||
- if pd == -1:
|
||
- print(kperf.error())
|
||
- return
|
||
- kperf.enable(pd)
|
||
- time.sleep(4)
|
||
- pmu_data = kperf.read(pd)
|
||
- for data in pmu_data.iter:
|
||
- print(f"evt:{data.evt} count:{data.count} tid:{data.tid} pid:{data.pid}")
|
||
- kperf.disable(pd)
|
||
- kperf.close(pd)
|
||
-
|
||
-
|
||
-def PerfList():
|
||
- event_iter = kperf.event_list(kperf.PmuEventType.CORE_EVENT)
|
||
- for event in event_iter:
|
||
- print(f"event: {event}")
|
||
-
|
||
-
|
||
-if __name__ == '__main__':
|
||
- Counting()
|
||
- PerfList()
|
||
- NewFork()
|
||
```
|
||
-
|
||
-
|
||
-#### Contribution
|
||
-
|
||
-1. Fork the repository
|
||
-2. Create Feat_xxx branch
|
||
-3. Commit your code
|
||
-4. Create Pull Request
|
||
-
|
||
-
|
||
-#### Gitee Feature
|
||
-
|
||
-1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||
-2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||
-3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||
-4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||
-5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||
-6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||
diff --git a/README.md b/README.md
|
||
index 0763ac8..505fb59 100644
|
||
--- a/README.md
|
||
+++ b/README.md
|
||
@@ -2,54 +2,25 @@
|
||
|
||
#### 描述
|
||
|
||
-实现了一个低开销的pmu集合库,为计数、采样和符号解析提供了抽象接口。
|
||
+libkperf是一个轻量级linux性能采集库,它能够让开发者以API的方式执行性能采集,包括pmu采样和符号解析。libkperf把采集数据内存化,使开发者能够在内存中直接处理采集数据,避免了读写perf.data带来的开销。
|
||
|
||
-#### 软件构架
|
||
-
|
||
-这个存储库包括两个模块:pmu集合和符号解析。
|
||
-
|
||
-Pmu收集模块是在syscall perf_event_open上开发的,用于启用内核pmu计数和采样,根据用户输入使用-thread或per-core模式。
|
||
-从环形缓冲区读取Pmu数据包,并将其解析为不同的结构,进行计数,采样和spe采样。
|
||
-对于采样,根据ips或pc从数据包中解析符号。每个符号包含符号名称、地址、源文件路径和行号(如果可能)。
|
||
-
|
||
-符号解析模块是在elfin-parser上开发的,elfin-parser是一个解析elf和dwarf的库。该模块以设计良好的数据结构管理所有符号数据,以实现快速查询。
|
||
-
|
||
-#### 下载
|
||
-
|
||
-git方法:
|
||
+#### 编译
|
||
|
||
+编译生成动态库和C的API:
|
||
```shell
|
||
git clone --recurse-submodules https://gitee.com/openeuler/libkperf.git
|
||
-```
|
||
-如果你只使用
|
||
-```shell
|
||
-git clone https://gitee.com/openeuler/libkperf.git
|
||
-```
|
||
-请再执行
|
||
-```shell
|
||
cd libkperf
|
||
-git submodule update --init --recursive
|
||
+bash build.sh install_path=/path/to/install
|
||
```
|
||
|
||
-无法使用git时:
|
||
-
|
||
-1.下载libkperf压缩包并解压。
|
||
-
|
||
-2.进入gitee上的libkperf的third_party目录,点击链接(如elfin-parser@13e57e2,点击@后面的提交ID),进行跳转并下载第三方库的压缩包,解压后放置于本地的libkperf项目的third_party目录。(elfin-parser对于安装是必须的)
|
||
-
|
||
-#### 安装
|
||
-
|
||
-运行bash脚本:
|
||
-
|
||
+如果想要编译调试版本:
|
||
```shell
|
||
-bash build.sh install_path=/home/libkperf
|
||
+bash build.sh install_path=/path/to/install buildType=debug
|
||
```
|
||
|
||
-如上,头文件和库将安装到/home/libkperf输出目录,installPath是可选参数,若没有设置,则默认安装到libkperf下的output目录。
|
||
-
|
||
-如果要额外增加python库支持,可以通过如下方式安装
|
||
+如果想要编译python包:
|
||
```shell
|
||
-bash build.sh python=true
|
||
+bash build.sh install_path=/path/to/install python=true
|
||
```
|
||
|
||
安装后若需要卸载python库, 可以执行下述命令
|
||
@@ -57,22 +28,23 @@ bash build.sh python=true
|
||
python3 -m pip uninstall -y libkperf
|
||
```
|
||
|
||
-#### 指令
|
||
+#### 文档
|
||
+详细文档可以参考docs目录:
|
||
+- [详细使用文档](./docs/Details.md)
|
||
|
||
-所有pmu功能都通过以下接口完成:
|
||
+#### 快速使用
|
||
|
||
+主要有以下几个API:
|
||
- PmuOpen
|
||
输入pid、core id和event,打开pmu设备。
|
||
- PmuEnable
|
||
开始收集。
|
||
- PmuRead
|
||
- 读取pmu数据并返回一个列表。
|
||
+ 读取采集数据。
|
||
- PmuDisable
|
||
停止收集。
|
||
- PmuClose
|
||
- 关闭PMU装置。
|
||
-
|
||
-API的详细说明请参考pmu.h。
|
||
+ 关闭pmu设备。
|
||
|
||
以下是一些示例:
|
||
|
||
@@ -101,7 +73,8 @@ PmuData *data = NULL;
|
||
// 读取PmuData,它是一个数组,长度是len。
|
||
int len = PmuRead(pd, &data);
|
||
for (int i = 0; i < len; ++i) {
|
||
- ...
|
||
+ PmuData *d = &data[i];
|
||
+ ...
|
||
}
|
||
// 释放PmuData。
|
||
PmuDataFree(data);
|
||
@@ -150,75 +123,6 @@ PmuDataFree(data);
|
||
// 类似fd,当任务结束时调用PmuClose释放资源。
|
||
PmuClose(pd);
|
||
```
|
||
-- 配置事件分组功能
|
||
-```C
|
||
-int pidList[1];
|
||
-pidList[0] = pid;
|
||
-unsigned numEvt = 16;
|
||
-char *evtList[numEvt] = {"r3", "r4", "r1", "r14", "r10", "r12", "r5", "r25",
|
||
- "r2", "r26", "r2d", "r17", "r8", "r22", "r24", "r11"};
|
||
-// 初始化事件列表,相同的group_id表示是同一个事件组
|
||
-// 如果不使用事件分组功能,需要配置事件组参数列表为空
|
||
-// 说明:如果事件的group_id为-1,表示此事件强制不进行分组
|
||
-PmuAttr attr = {0};
|
||
-attr.evtList = evtList;
|
||
-attr.numEvt = numEvt;
|
||
-attr.pidList = pidList;
|
||
-attr.numPid = 1;
|
||
-struct EvttAttr groupId[numEvt] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 13, 13};
|
||
-attr.evtAttr = groupId;
|
||
-// 调用PmuOpen,返回pd。pd表示该任务的id。
|
||
-int pd = PmuOpen(COUNTING, &attr);
|
||
-// 开始采集。
|
||
-PmuEnable(pd);
|
||
-// 采集1秒。
|
||
-sleep(1);
|
||
-// 停止采集。
|
||
-PmuDisable(pd);
|
||
-PmuData *data = NULL;
|
||
-// 读取PmuData,它是一个数组,长度是len。
|
||
-int len = PmuRead(pd, &data);
|
||
-for (int i = 0; i < len; ++i) {
|
||
- ...
|
||
-}
|
||
-// 释放PmuData。
|
||
-PmuDataFree(data);
|
||
-// 类似fd,当任务结束时调用PmuClose释放资源。
|
||
-PmuClose(pd);
|
||
-```
|
||
-
|
||
-- Counting模式支持采集fork新生成子进程的能力
|
||
-```C
|
||
-int pidList[1];
|
||
-pidList[0] = pid;
|
||
-unsigned numEvt = 1;
|
||
-char *evtList[numEvt] = {"cycles"};
|
||
-PmuAttr attr = {0};
|
||
-attr.evtList = evtList;
|
||
-attr.numEvt = numEvt;
|
||
-attr.pidList = pidList;
|
||
-attr.numPid = 1;
|
||
-// 增加参数includeNewFork,为1可获取新生成子进程的数据,默认不获取
|
||
-attr.includeNewFork = 1;
|
||
-// 调用PmuOpen,返回pd。pd表示该任务的id。
|
||
-int pd = PmuOpen(COUNTING, &attr);
|
||
-// 开始采集。
|
||
-PmuEnable(pd);
|
||
-// 采集1秒。
|
||
-sleep(1);
|
||
-// 停止采集。
|
||
-PmuDisable(pd);
|
||
-PmuData *data = NULL;
|
||
-// 读取PmuData,它是一个数组,长度是len。
|
||
-int len = PmuRead(pd, &data);
|
||
-for (int i = 0; i < len; ++i) {
|
||
- ...
|
||
-}
|
||
-// 释放PmuData。
|
||
-PmuDataFree(data);
|
||
-// 类似fd,当任务结束时调用PmuClose释放资源。
|
||
-PmuClose(pd);
|
||
-```
|
||
|
||
Python 例子:
|
||
```python
|
||
@@ -230,8 +134,7 @@ import kperf
|
||
|
||
def Counting():
|
||
evtList = ["r11", "cycles"]
|
||
- evtAttr = [2, 2] # 与事件列表对应的事件分组id列表,相同的事件id表示是同一个事件组; 不启用的话,可以不使用这个参数
|
||
- pmu_attr = kperf.PmuAttr(evtList=evtList, evtAttr=evtAttr)
|
||
+ pmu_attr = kperf.PmuAttr(evtList=evtList)
|
||
pd = kperf.open(kperf.PmuTaskType.COUNTING, pmu_attr)
|
||
if pd == -1:
|
||
print(kperf.errorno())
|
||
@@ -252,50 +155,4 @@ def Counting():
|
||
kperf.disable(pd)
|
||
kperf.close(pd)
|
||
|
||
-
|
||
-def NewFork():
|
||
- # test_new_fork demo in test_perf, you can find test_new_fork.cpp
|
||
- p=subprocess.Popen(['test_new_fork']);
|
||
- pidList=[p.pid]
|
||
- evtList=["cycles"]
|
||
- pmu_attr = kperf.PmuAttr(evtList=evtList, includeNewFork=True, pidList=pidList)
|
||
- pd = kperf.open(kperf.PmuTaskType.COUNTING, pmu_attr)
|
||
- if pd == -1:
|
||
- print(kperf.error())
|
||
- return
|
||
- kperf.enable(pd)
|
||
- time.sleep(4)
|
||
- pmu_data = kperf.read(pd)
|
||
- for data in pmu_data.iter:
|
||
- print(f"evt:{data.evt} count:{data.count} tid:{data.tid} pid:{data.pid}")
|
||
- kperf.disable(pd)
|
||
- kperf.close(pd)
|
||
-
|
||
-
|
||
-def PerfList():
|
||
- event_iter = kperf.event_list(kperf.PmuEventType.CORE_EVENT)
|
||
- for event in event_iter:
|
||
- print(f"event: {event}")
|
||
-
|
||
-
|
||
-if __name__ == '__main__':
|
||
- Counting()
|
||
- PerfList()
|
||
```
|
||
-
|
||
-#### 参与贡献
|
||
-
|
||
-1. Fork 本仓库
|
||
-2. 新建 Feat_xxx 分支
|
||
-3. 提交代码
|
||
-4. 新建 Pull Request
|
||
-
|
||
-
|
||
-#### 特技
|
||
-
|
||
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||
-2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||
-4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||
-5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||
-6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||
diff --git a/docs/Details.md b/docs/Details.md
|
||
new file mode 100644
|
||
index 0000000..db0b407
|
||
--- /dev/null
|
||
+++ b/docs/Details.md
|
||
@@ -0,0 +1,264 @@
|
||
+Details
|
||
+============
|
||
+### Counting
|
||
+libkperf提供Counting模式,类似于perf stat功能。
|
||
+例如,如下perf命令:
|
||
+```
|
||
+perf stat -e cycles,branch-misses
|
||
+```
|
||
+该命令是对系统采集cycles和branch-misses这两个事件的计数。
|
||
+
|
||
+对于libkperf,可以这样来设置PmuAttr:
|
||
+```c++
|
||
+char *evtList[2];
|
||
+evtList[0] = "cycles";
|
||
+evtList[1] = "branch-misses";
|
||
+PmuAttr attr = {0};
|
||
+attr.evtList = evtList;
|
||
+attr.numEvt = 2;
|
||
+int pd = PmuOpen(COUNTING, &attr);
|
||
+```
|
||
+通过调用```PmuOpen```初始化了采集任务,并获得了任务的标识符pd。
|
||
+然后,可以利用pd来启动采集:
|
||
+```c++
|
||
+PmuEnable(pd);
|
||
+sleep(any_duration);
|
||
+PmuDisable(pd);
|
||
+```
|
||
+不论是否停止了采集,都可以通过```PmuRead```来读取采集数据:
|
||
+```c++
|
||
+PmuData *data = NULL;
|
||
+int len = PmuRead(pd, &data);
|
||
+```
|
||
+```PmuRead```会返回采集数据的长度。
|
||
+如果是对系统采集,那么PmuData的长度等于core的数量乘以事件的数量,PmuData的数据类似如下:
|
||
+```
|
||
+cpu 0 count 123 evt cycles
|
||
+cpu 1 count 1242354 evt cycles
|
||
+cpu 2 count 7897234 evt cycles
|
||
+...
|
||
+cpu 0 count 423423 evt branch-misses
|
||
+cpu 1 count 124235 evt branch-misses
|
||
+cpu 2 count 789723 evt branch-misses
|
||
+...
|
||
+```
|
||
+如果是对进程采集,那么PmuData的长度等于进程内线程的数量乘以事件的数量,PmuData的数据类似如下:
|
||
+```
|
||
+pid 4156 tid 4156 count 123 evt cycles
|
||
+pid 4156 tid 4157 count 534123 evt cycles
|
||
+pid 4156 tid 4158 count 1241244 evt cycles
|
||
+...
|
||
+pid 4156 tid 4156 count 12414 evt branch-misses
|
||
+pid 4156 tid 4157 count 5123 evt branch-misses
|
||
+pid 4156 tid 4158 count 64574 evt branch-misses
|
||
+...
|
||
+```
|
||
+
|
||
+### Sampling
|
||
+libkperf提供Sampling模式,类似于perf record的如下命令:
|
||
+```
|
||
+perf record -e cycles,branch-misses
|
||
+```
|
||
+该命令是对系统采样cycles和branch-misses这两个事件。
|
||
+
|
||
+设置PmuAttr的方式和Counting一样,在调用PmuOpen的时候,把任务类型设置为SAMPLING,并且设置采样频率:
|
||
+```c++
|
||
+// 采样频率是1000HZ
|
||
+attr.freq = 1000;
|
||
+attr.useFreq = 1;
|
||
+int pd = PmuOpen(SAMPLING, &attr);
|
||
+```
|
||
+
|
||
+启动采集和读取数据的方式和Counting一致。
|
||
+如果是对系统采集,PmuData的数据类似如下(长度取决于数据量):
|
||
+```
|
||
+cpu 0 pid 3145 tid 3145 period 12314352
|
||
+cpu 0 pid 4145 tid 4145 period 12314367
|
||
+...
|
||
+cpu 1 pid 23423 tid 23423 period 1231241
|
||
+...
|
||
+...
|
||
+```
|
||
+如果是对进程采集,PmuData的数据类似如下:
|
||
+```
|
||
+cpu 32 pid 7878 tid 7878 period 123144
|
||
+cpu 32 pid 7878 tid 7879 period 1523342
|
||
+cpu 32 pid 7878 tid 7879 period 1234342
|
||
+...
|
||
+```
|
||
+每一条记录还包含触发事件的程序地址和符号信息,关于如何获取符号信息,可以参考[获取符号信息](#获取符号信息)这一章节。
|
||
+
|
||
+### SPE Sampling
|
||
+libkperf提供SPE采样模式,类似于perf record的如下命令:
|
||
+```
|
||
+perf record -e arm_spe_0/load_filter=1/
|
||
+```
|
||
+该命令是对系统进行spe采样,关于linux spe采样的详细介绍,可以参考[这里](https://www.man7.org/linux/man-pages/man1/perf-arm-spe.1.html)。
|
||
+
|
||
+对于libkperf,可以这样设置PmuAttr:
|
||
+```c++
|
||
+PmuAttr attr = {0};
|
||
+// 采样周期是8192
|
||
+attr.period = 8192;
|
||
+// 设置filter属性为load_filter
|
||
+attr.dataFilter = LOAD_FILTER;
|
||
+```
|
||
+对于spe采样,不需要设置evtList,而是通过设置dataFilter和evFilter来指定需要采集的事件。dataFilter和evFilter的含义仍然可以参考[perf spe的说明文档](https://www.man7.org/linux/man-pages/man1/perf-arm-spe.1.html)。
|
||
+
|
||
+采样数据PmuData和Sampling模式差不多,差别是:
|
||
+- SPE采样的调用栈只有一层,而Sampling可以有多层调用栈。
|
||
+- SPE的PmuData提供了额外的数据struct PmuDataExt *ext.
|
||
+PmuDataExt包含spe特有的数据:访存的物理地址、虚拟地址和事件bit。
|
||
+```c++
|
||
+struct PmuDataExt {
|
||
+ unsigned long pa; // physical address
|
||
+ unsigned long va; // virtual address
|
||
+ unsigned long event; // event id, which is a bit map of mixed events, event bit is defined in SPE_EVENTS.
|
||
+};
|
||
+```
|
||
+其中,物理地址pa需要在启用PA_ENABLE的情况下才能采集。
|
||
+event是一个bit map,是多个事件的集合,每一个事件占据一个bit,事件对应的bit参考枚举SPE_EVENTS:
|
||
+```c++
|
||
+enum SPE_EVENTS {
|
||
+ SPE_EV_EXCEPT = 1 << 0,
|
||
+ SPE_EV_RETIRED = 1 << 1,
|
||
+ SPE_EV_L1D_ACCESS = 1 << 2,
|
||
+ SPE_EV_L1D_REFILL = 1 << 3,
|
||
+ SPE_EV_TLB_ACCESS = 1 << 4,
|
||
+ SPE_EV_TLB_WALK = 1 << 5,
|
||
+ SPE_EV_NOT_TAKEN = 1 << 6,
|
||
+ SPE_EV_MISPRED = 1 << 7,
|
||
+ SPE_EV_LLC_ACCESS = 1 << 8,
|
||
+ SPE_EV_LLC_MISS = 1 << 9,
|
||
+ SPE_EV_REMOTE_ACCESS= 1 << 10,
|
||
+ SPE_EV_ALIGNMENT = 1 << 11,
|
||
+ SPE_EV_PARTIAL_PRED = 1 << 17,
|
||
+ SPE_EV_EMPTY_PRED = 1 << 18,
|
||
+};
|
||
+```
|
||
+
|
||
+### 获取符号信息
|
||
+结构体PmuData里提供了采样数据的调用栈信息,包含调用栈的地址、符号名称等。
|
||
+```c++
|
||
+struct Symbol {
|
||
+ unsigned long addr;
|
||
+ char* module;
|
||
+ char* symbolName;
|
||
+ char* fileName;
|
||
+ unsigned int lineNum;
|
||
+ ...
|
||
+};
|
||
+
|
||
+struct Stack {
|
||
+ struct Symbol* symbol;
|
||
+ struct Stack* next;
|
||
+ struct Stack* prev;
|
||
+ ...
|
||
+} __attribute__((aligned(64)));
|
||
+```
|
||
+
|
||
+Stack是链表结构,每一个元素都是一层调用函数。
|
||
+```mermaid
|
||
+graph LR
|
||
+a(Symbol) --> b(Symbol)
|
||
+b --> c(Symbol)
|
||
+c --> d(......)
|
||
+```
|
||
+
|
||
+Symbol的字段信息受PmuAttr影响:
|
||
+- PmuAttr.callStack会决定Stack是完整的调用栈,还是只有一层调用栈(即Stack链表只有一个元素)。
|
||
+- PmuAttr.symbolMode如果等于NO_SYMBOL_RESOLVE,那么PmuData的stack是空指针。
|
||
+- PmuAttr.symbolMode如果等于RESOLVE_ELF,那么Symbol的fileName和lineNum没有数据,都等于0,因为没有解析dwarf信息。
|
||
+- PmuAttr.symbolMode如果等于RESOLVE_ELF_DWARF,那么Symbol的所有信息都有效。
|
||
+
|
||
+### 采集uncore事件
|
||
+libkperf支持uncore事件的采集,只有Counting模式支持uncore事件的采集(和perf一致)。
|
||
+可以像这样设置PmuAttr:
|
||
+```c++
|
||
+char *evtList[1];
|
||
+evtList[0] = "hisi_sccl1_ddrc0/flux_rd/";
|
||
+PmuAttr attr = {0};
|
||
+attr.evtList = evtList;
|
||
+attr.numEvt = 1;
|
||
+int pd = PmuOpen(COUNTING, &attr);
|
||
+```
|
||
+uncore事件的格式为```<device>/<event>/```,上面代码是采集设备hisi_sccl1_ddrc0的flux_rd事件。
|
||
+
|
||
+也可以把设备索引号省略:
|
||
+```c++
|
||
+evtList[0] = "hisi_sccl1_ddrc/flux_rd/";
|
||
+```
|
||
+这里把hisi_sccl1_ddrc0改为了hisi_sccl1_ddrc,这样会采集设备hisi_sccl1_ddrc0、hisi_sccl1_ddrc1、hisi_sccl1_ddrc2...,并且采集数据PmuData是所有设备数据的总和:count = count(hisi_sccl1_ddrc0) + count(hisi_sccl1_ddrc1) + count(hisi_sccl1_ddrc2) + ...
|
||
+
|
||
+也可以通过```<device>/config=0xxx/```的方式来指定事件名:
|
||
+```c++
|
||
+evtList[0] = "hisi_sccl1_ddrc0/config=0x1/";
|
||
+```
|
||
+这样效果是和指定flux_rd是一样的。
|
||
+
|
||
+### 采集tracepoint
|
||
+libkperf支持tracepoint的采集,支持的tracepoint事件可以通过perf list来查看(通常需要root权限)。
|
||
+可以这样设置PmuAttr:
|
||
+```c++
|
||
+char *evtList[1];
|
||
+evtList[0] = "sched:sched_switch";
|
||
+PmuAttr attr = {0};
|
||
+attr.evtList = evtList;
|
||
+attr.numEvt = 1;
|
||
+```
|
||
+
|
||
+tracepoint支持Counting和Sampling两种模式,API调用流程和两者相似。
|
||
+tracepoint能够获取每个事件特有的数据,比如sched:sched_switch包含的数据有:prev_comm, prev_pid, prev_prio, prev_state, next_comm, next_pid, next_prio.
|
||
+想要查询每个事件包含哪些数据,可以查看/sys/kernel/tracing/events下面的文件内容,比如/sys/kernel/tracing/events/sched/sched_switch/format。
|
||
+
|
||
+libkperf提供了接口PmuGetField来获取tracepoint的数据。比如对于sched:sched_switch,可以这样调用:
|
||
+```c++
|
||
+int prev_pid;
|
||
+PmuGetField(pmuData->rawData, "prev_pid", &prev_pid, sizeof(prev_pid));
|
||
+char next_comm[16];
|
||
+PmuGetField(pmuData->rawData, "next_comm", &next_comm, sizeof(next_comm));
|
||
+```
|
||
+这里调用者需要提前了解数据的类型,并且指定数据的大小。数据的类型和大小仍然可以从/sys/kernel/tracing/下每个事件的format文件来得知。
|
||
+
|
||
+### 事件分组
|
||
+libkperf提供了事件分组的能力,能够让多个事件同时处于采集状态。
|
||
+该功能类似于perf的如下使用方式:
|
||
+```
|
||
+perf stat -e "{cycles,branch-loads,branch-load-misses,iTLB-loads}",inst_retired
|
||
+```
|
||
+
|
||
+对于libkperf,可以通过设置PmuAttr的evtAttr字段来设定哪些事件放在一个group内。
|
||
+比如,可以这样调用:
|
||
+```c
|
||
+unsigned numEvt = 5;
|
||
+char *evtList[numEvt] = {"cycles","branch-loads","branch-load-misses","iTLB-loads","inst_retired"};
|
||
+// 前四个事件是一个分组
|
||
+struct EvtAttr groupId[numEvt] = {1,1,1,1,-1};
|
||
+PmuAttr attr = {0};
|
||
+attr.evtList = evtList;
|
||
+attr.numEvt = numEvt;
|
||
+attr.evtAttr = groupId;
|
||
+```
|
||
+上述代码把前四个事件设定为一个分组,groupId都设定为1,最后一个事件不分组,groupId设定为-1。
|
||
+事件数组attr.evtList和事件属性数组attr.evtAttr必须一一对应,即长度必须一致。
|
||
+或者attr.evtAttr也可以是空指针,那么所有事件都不分组。
|
||
+
|
||
+事件分组的效果可以从PmuData.countPercent来体现。PmuData.countPercent表示事件实际采集时间除以事件期望采集时间。
|
||
+对于同一组的事件,他们的countPercent是相同的。如果一个组的事件过多,超过了硬件计数器的数目,那么这个组的所有事件都不会被采集,countPercent会等于-1.
|
||
+
|
||
+### 对进程子线程计数采集
|
||
+```mermaid
|
||
+graph TD
|
||
+a(主线程) --perf stat--> b(创建线程)
|
||
+b --> c(子线程)
|
||
+c --end perf--> d(子线程退出)
|
||
+```
|
||
+考虑上面的场景:用perf stat对进程采集,之后进程创建了子线程,采集一段事件后,停止perf。
|
||
+查看采集结果,perf只会显示主线程的采集结果,而无法看到子线程的结果:count = count(main thread) + count(thread). perf把子线程的数据聚合到了主线程上。
|
||
+
|
||
+libkperf提供了采集子线程的能力。如果想要在上面场景中获取子线程的计数,可以把PmuAttr.incluceNewFork设置为1.
|
||
+```c++
|
||
+attr.includeNewFork = 1;
|
||
+```
|
||
+然后,通过PmuRead获取到的PmuData,便能包含子线程计数信息了。
|
||
+注意,该功能是针对Counting模式,因为Sampling和SPE Sampling本身就会采集子线程的数据。
|
||
\ No newline at end of file
|
||
--
|
||
2.43.0
|
||
|