1# Perf调测 2 3 4## 基本概念 5 6Perf为性能分析工具,依赖PMU(Performance Monitoring Unit)对采样事件进行计数和上下文采集,统计出热点分布(hot spot)和热路径(hot path)。 7 8 9## 运行机制 10 11基于事件采样原理,以性能事件为基础,当事件发生时,相应的事件计数器溢出发生中断,在中断处理函数中记录事件信息,包括当前的pc、当前运行的任务ID以及调用栈等信息。 12 13Perf提供2种工作模式,计数模式和采样模式。 14 15计数模式仅统计事件发生的次数和耗时,采样模式会收集上下文数据到环形buffer中,需要IDE进行数据解析生成热点函数与热点路径。 16 17 18## 接口说明 19 20OpenHarmony LiteOS-A内核的Perf模块提供下面几种功能,接口详细信息可以查看[API](https://gitee.com/openharmony/kernel_liteos_a/blob/master/kernel/include/los_perf.h)参考。 21 22 **表1** Perf模块接口说明 23 24| 功能分类 | 接口描述 | 25| -------- | -------- | 26| 开启/停止Perf采样 | LOS_PerfInit : 初始化Perf<br/>LOS_PerfStart:开启采样<br/>LOS_PerfStop:停止采样 | 27| 配置Perf采样事件 | LOS_PerfConfig:配置采样事件的类型、周期等 | 28| 读取采样数据 | LOS_PerfDataRead:读取采样数据到指定地址 | 29| 注册采样数据缓冲区的钩子函数 | LOS_PerfNotifyHookReg:注册缓冲区水线到达的处理钩子<br/>LOS_PerfFlushHookReg:注册缓冲区刷cache的钩子 | 30 31 321. Perf采样事件的结构体为PerfConfigAttr,详细字段含义及取值详见 [kernel\include\los_perf.h](https://gitee.com/openharmony/kernel_liteos_a/blob/master/kernel/include/los_perf.h) 。 33 342. 采样数据缓冲区为环形buffer,buffer中读过的区域可以覆盖写,未被读过的区域不能被覆盖写。 35 363. 缓冲区有限,用户可通过注册水线到达的钩子进行buffer溢出提醒或buffer读操作。默认水线值为buffer总大小的1/2。 示例如下: 37 38 ```c 39 VOID Example_PerfNotifyHook(VOID) 40 { 41 CHAR buf[LOSCFG_PERF_BUFFER_SIZE] = {0}; 42 UINT32 len; 43 PRINT_DEBUG("perf buffer reach the waterline!\n"); 44 len = LOS_PerfDataRead(buf, LOSCFG_PERF_BUFFER_SIZE); 45 OsPrintBuff(buf, len); /* print data */ 46 } 47 LOS_PerfNotifyHookReg(Example_PerfNotifyHook); 48 ``` 49 504. 若perf采样的buffer涉及到CPU跨cache,则用户可通过注册刷cache的钩子,进行cache同步。 示例如下: 51 52 ```c 53 VOID Example_PerfFlushHook(VOID *addr, UINT32 size) 54 { 55 OsCacheFlush(addr, size); /* platform interface */ 56 } 57 LOS_PerfNotifyHookReg(Example_PerfFlushHook); 58 ``` 59 60 刷cache接口视具体的平台自行配置。 61 62 63## 开发指导 64 65 66### 内核态开发流程 67 68开启Perf调测的典型流程如下: 69 701. 配置Perf模块相关宏。 71 配置Perf控制宏LOSCFG_KERNEL_PERF,默认关,在kernel/liteos_a目录下执行 make update_config命令配置"Kernel->Enable Perf Feature"中打开: 72 73 | 配置项 | menuconfig选项 | 含义 | 设置值 | 74 | -------- | -------- | -------- | -------- | 75 | LOSCFG_KERNEL_PERF | Enable Perf Feature | Perf模块的裁剪开关 | YES/NO | 76 | LOSCFG_PERF_CALC_TIME_BY_TICK | Time-consuming Calc Methods->By Tick | Perf计时单位为tick | YES/NO | 77 | LOSCFG_PERF_CALC_TIME_BY_CYCLE | Time-consuming Calc Methods->By Cpu Cycle | Perf计时单位为cycle | YES/NO | 78 | LOSCFG_PERF_BUFFER_SIZE | Perf Sampling Buffer Size | Perf采样buffer的大小 | INT | 79 | LOSCFG_PERF_HW_PMU | Enable Hardware Pmu Events for Sampling | 使能硬件PMU事件,需要目标平台支持硬件PMU | YES/NO | 80 | LOSCFG_PERF_TIMED_PMU | Enable Hrtimer Period Events for Sampling | 使能高精度周期事件,需要目标平台支持高精度定时器 | YES/NO | 81 | LOSCFG_PERF_SW_PMU | Enable Software Events for Sampling | 使能软件事件,需要开启LOSCFG_KERNEL_HOOK | YES/NO | 82 832. 调用LOS_PerfConfig配置需要采样的事件。 84 Perf提供2种模式的配置,及3大类型的事件配置: 85 86 2种模式:计数模式(仅统计事件发生次数)、采样模式(收集上下文如任务ID、pc、backtrace等)。 87 88 3种事件类型:CPU硬件事件(cycle、branch、icache、dcache等)、高精度周期事件(cpu clock)、OS软件事件(task switch、mux pend、irq等)。 89 903. 在需要采样的代码起始点调用LOS_PerfStart(UINT32 sectionId), 入参sectionId标记不同的采样回话id。 91 924. 在需要采样的代码结束点调用LOS_PerfStop。 93 945. 调用输出缓冲区数据的接口LOS_PerfDataRead读取采样数据,并使用IDE工具进行解析。 95 96 97#### 内核态编程实例 98 99本实例实现如下功能: 100 1011. 创建perf测试任务。 102 1032. 配置采样事件。 104 1053. 启动perf。 106 1074. 执行需要统计的算法。 108 1095. 停止perf。 110 1116. 输出统计结果。 112 113 114#### 内核态示例代码 115 116前提条件:在menuconfig菜单中完成perf模块的配置, 并勾选Enable Hook Feature,Enable Software Events for Sampling。 117 118为方便学习,本演示代码直接在 . kernel /liteos_a/testsuites /kernel /src /osTest.c中编译验证即可。 119 120实例代码如下: 121 122```c 123#include "los_perf.h" 124#define TEST_MALLOC_SIZE 200 125#define TEST_TIME 5 126 127/* 验证函数中进行malloc和free */ 128VOID test(VOID) 129{ 130 VOID *p = NULL; 131 int i; 132 for (i = 0; i < TEST_TIME; i++) { 133 p = LOS_MemAlloc(m_aucSysMem1, TEST_MALLOC_SIZE); 134 if (p == NULL) { 135 PRINT_ERR("test alloc failed\n"); 136 return; 137 } 138 139 (VOID)LOS_MemFree(m_aucSysMem1, p); 140 } 141} 142 143STATIC VOID OsPrintBuff(const CHAR *buf, UINT32 num) 144{ 145 UINT32 i = 0; 146 PRINTK("num: "); 147 for (i = 0; i < num; i++) { 148 PRINTK(" %02d", i); 149 } 150 PRINTK("\n"); 151 PRINTK("hex: "); 152 for (i = 0; i < num; i++) { 153 PRINTK(" %02x", buf[i]); 154 } 155 PRINTK("\n"); 156} 157STATIC VOID perfTestHwEvent(VOID) 158{ 159 UINT32 ret; 160 CHAR *buf = NULL; 161 UINT32 len; 162 163 //LOS_PerfInit(NULL, 0); 164 165 166 PerfConfigAttr attr = { 167 .eventsCfg = { 168 .type = PERF_EVENT_TYPE_SW, 169 .events = { 170 [0] = {PERF_COUNT_SW_TASK_SWITCH, 0xff}, /* 抓取调度 */ 171 [1] = {PERF_COUNT_SW_MEM_ALLOC, 0xff}, /* 抓取内存分配 */ 172 173 PERF_COUNT_SW_TASK_SWITCH 174 }, 175 .eventsNr = 2, 176 .predivided = 1, /* cycle counter increase every 64 cycles */ 177 }, 178 .taskIds = {0}, 179 .taskIdsNr = 0, 180 .needSample = 0, 181 .sampleType = PERF_RECORD_IP | PERF_RECORD_CALLCHAIN, 182 }; 183 ret = LOS_PerfConfig(&attr); 184 if (ret != LOS_OK) { 185 PRINT_ERR("perf config error %u\n", ret); 186 return; 187 } 188 PRINTK("------count mode------\n"); 189 LOS_PerfStart(0); 190 test(); /* this is any test function*/ 191 LOS_PerfStop(); 192 PRINTK("--------sample mode------ \n"); 193 attr.needSample = 1; 194 LOS_PerfConfig(&attr); 195 LOS_PerfStart(2); // 2: set the section id to 2. 196 test(); /* this is any test function*/ 197 LOS_PerfStop(); 198 buf = LOS_MemAlloc(m_aucSysMem1, LOSCFG_PERF_BUFFER_SIZE); 199 if (buf == NULL) { 200 PRINT_ERR("buffer alloc failed\n"); 201 return; 202 } 203 /* get sample data */ 204 len = LOS_PerfDataRead(buf, LOSCFG_PERF_BUFFER_SIZE); 205 OsPrintBuff(buf, len); /* print data */ 206 (VOID)LOS_MemFree(m_aucSysMem1, buf); 207} 208 209UINT32 Example_Perf_test(VOID) 210{ 211 UINT32 ret; 212 TSK_INIT_PARAM_S perfTestTask = {0}; 213 UINT32 taskID; 214 /* 创建用于perf测试的任务 */ 215 perfTestTask.pfnTaskEntry = (TSK_ENTRY_FUNC)perfTestHwEvent; 216 perfTestTask.pcName = "TestPerfTsk"; /* 测试任务名称 */ 217 perfTestTask.uwStackSize = 0x1000; // 0x8000: perf test task stack size 218 perfTestTask.usTaskPrio = 5; // 5: perf test task priority 219 ret = LOS_TaskCreate(&taskID, &perfTestTask); 220 if (ret != LOS_OK) { 221 PRINT_ERR("PerfTestTask create failed. 0x%x\n", ret); 222 return LOS_NOK; 223 } 224 return LOS_OK; 225} 226LOS_MODULE_INIT(perfTestHwEvent, LOS_INIT_LEVEL_KMOD_EXTENDED); 227``` 228 229 230#### 内核态结果验证 231 232 输出结果如下: 233 234``` 235type: 2 236events[0]: 1, 0xff 237events[1]: 3, 0xff 238predivided: 1 239sampleType: 0x60 240needSample: 0 241------count mode------ 242[task switch] eventType: 0x1 [core 0]: 0 243[mem alloc] eventType: 0x3 [core 0]: 5 244time used: 0.005000(s) 245--------sample mode------ 246type: 2 247events[0]: 1, 0xff 248events[1]: 3, 0xff 249predivided: 1 250sampleType: 0x60 251needSample: 1 252dump perf data, addr: 0x402c3e6c length: 0x5000 253time used: 0.000000(s) 254num: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 255hex: 00 ffffffef ffffffef ffffffef 02 00 00 00 14 00 00 00 60 00 00 00 02 00 00 00 256 257根据实际运行环境,过程打印会有差异 258``` 259 260- 针对计数模式,系统在perf stop后会打印: 261 事件名称(cycles)、事件类型(0xff)、事件发生的次数(5466989440)。 262 263 当采样事件为硬件PMU事件时,打印的事件类型为实际的硬件事件id,非enum PmuHWId中定义的抽象类型。 264 265- 针对采样模式,系统在perf stop后会打印采样数据的地址和长度: 266 dump section data, addr: (0x8000000) length: (0x5000) 267 268 用户可以通过JTAG口导出该片内存,再使用IDE线下工具解析。 269 270 或者通过LOS_PerfDataRead将数据读到指定地址,进行查看或进一步处理。示例中OsPrintBuff为测试接口,其按字节打印Read到的采样数据,num表示第几个字节,hex表示该字节中的数值。 271