源头活水-perf events介绍

335 阅读6分钟

大家好,我是程栩,一个专注于性能的大厂程序员,分享包括但不限于计算机体系结构、性能优化、云原生的知识。

今天我们接着聊perf,聊一聊perf数据的来源,或者说perf事件。本文是perf系列的第三篇文章,后续会继续介绍perf,包括用法、原理和相关的经典文章。

前面我们说过,perf是事件驱动的,而事件有多种类别,分别在计算机全栈的不同位置。我们今天将介绍这些事件,并进行一些简单的描述,后续将逐个进行原理的解说。

软件事件(Software events)

软件事件可以主要认为是内核的事件。打开perf list我们可以看到一些预定义的内核事件,比如我们前文提到过的cpu-clockcontext-switches等。如果想要知道具体的事件的含义,可以在perf_event_open的文档中进行查看。perf_event_openperf使用的系统调用,perf将参数传入到里面再获取到相应的数据,这是perf的原理,后续我们也会有相关的介绍。

在这里,我们给出一些事件相关的含义:

事件名含义
cpu-clockcpu-clock,可以看成是cpu执行的计数器
task-clock某个任务在cpu上运行时的clock数
page-faults缺页中断的数量,如果过多的话会影响程序的运行速度
context-switches上下文切换的数量,如果切换的过多,可能想要执行的任务不一定能有足够的cpu时间
cpu-migrations任务在CPU间迁移的数量,过多的话可能会导致缓存失效
minor-faults是缺页中断的一部分,指的是访问在内存中但是没有映射到程序地址空间的页发生的错误
major-faults是缺页中断的一部分,指的是访问不在内存中的页,需要磁盘参与进行换页的中断
emulation-faults当内核遇到一些没有实现的指令时,可能会在用户空间模拟该执行,过多会影响运算效率
dummy一个类似占位符的事件,可以让用户收集数据而不需要一个计数事件

硬件事件(Hardware events)

硬件事件是perf的出发点,让用户可以通过该机制获取到CPU上性能监控单元(PMU:performance monitoring unit )的数据,从而帮助用户了解硬件瓶颈在哪。这一部分的事件主要依赖于硬件厂商的支持,硬件厂商也在逐渐的给出更多更精确的硬件事件。值得注意的是,尽管硬件事件是在硬件实现,也不意味着这不会带来负载。假设我们用perf进行数据的收集,perf自身也是一个软件,处理硬件数据也会有一定的负载。同时,由于硬件事件需要通知软件来拿数据,会导致部分数据是失真的。比如在A时刻某事件计数器触发了,内核在A+2周期来拿数据,这会导致数据的失真。我们可以参考硬件厂商的手册来了解硬件事件,后续我们也会介绍类似PEBS这样的更高级的硬件事件特性。此外,在一般的虚拟机中,是不会有

同样的,我们也在这里给出一些硬件事件,以及他们相关的含义:

事件名含义
cache-misses缓存不命中的情况,过多的话会导致程序运行过慢
branch-misses分支预测失败的指令情况,过多的分支预测失败也会影响程序的运行速度
cpu-cyclescpu的时钟情况,和cpu-clock不是一个概念
stalled_cycles_frontendcpu前端的停滞周期数,cpu的前端是负责解码的部分,停滞可能是因为I-cache失效,如果停滞会导致后端空转
stalled_cycles_backendcpu后端的停滞周期数,cpu的后端负责执行前端解出来的微指令,停滞可能是因为指令的关键路径较长或者访存拖慢了运行

内核追踪点(Kernel Tracepoints)

内核追踪点是被硬编码在内核中的钩子,当内核执行到某个地方的时候,就可能会触发到函数,从而将内核中的数据暴露出来。内核追踪点分布在内核的调度、内存管理、文件管理等模块。

由于是硬编码在内核中,优点的话就是其是静态的,不会影响内核本身的运行,相对应的负载也比较少;缺点也自然很明显,由于是硬编码的,那么每次更改就需要重新编译内核,相对而言更新的成本比较高。考虑到兼容性,内核追踪点的接口一般是不变化的。

我们可以用如下的方式获取到追踪点信息:

[root@VM-16-2-centos ~]#  perf list | awk -F: '/Tracepoint event/ { lib[$1]++ } END {
>     for (l in lib) { printf "  %-16.16s %d\n", l, lib[l] } }' | sort | column

内核追踪点主要包含以下几个模块:

  • block:块设备I/O
  • ext4:文件系统操作
  • kmem:内核内存分配
  • random:内核随机数生成器
  • sched:CPU调度程序事件
  • syscalls:系统调用进入和退出

一般来说,内核追踪点调用格式为模块名+事件。

USDT(User-Level Statically Defined Tracing)

USDT和内核追踪点类似,也是硬编码的,不过是硬编码在用户态的程序中。当我们面对一个黑箱一样的二进制时,如果其开启了USDT,我们就可以得到其给出的一些运行信息。USDT允许用户在应用程序一些特定的位置加入探针,从而帮助用户来获取数据。

其优点也比较明显,可以帮助我们不修改源码进行调试;但是这种调试是基于已有探针的情况,如何加入探针则又是一件令人头疼的事情,这是USDT的缺点。

Dynamic Tracing

动态追踪和追踪点类似,但并不完全一致,给出如下的两个图:

静态追踪点和动态追踪的区别

动态追踪允许我们查看系统上发生的任何事情,这意味着我们可以在不改动代码的情况下就获取到更多的信息。但是它的缺点也很明显,那就是会随着内核的变化而变化。

动态追踪借助了kprobe这样一个机制来实现,而USDT则借助uprobe来实现,具体如何我们会在后续继续介绍。

总结静态追踪点、USDT和动态追踪,我们会发现,变化越少的能力相对越弱,变化越大的能力则相对较强。

Timed Profiling

时序采样的数据来源于在具体频率下进行切片后的总和,也即perf record。相比于前面的部分,更有时间的概念,可以帮助统计一段时间里的综合。

小结

今天我们更详细的介绍了perf相关的数据来源,其在思维导图中的部分如下图所示:

小结

关注我,学习更多性能知识,一起攀登性能之巅。

参考资料