perf

概述

perf 是一个非常有用的性能分析工具,可以同时剖析内核态与用户态。
它的代码直接包含在了linux内核的主线中。在 ubuntu 中,可以通过安装 linux-tool 获取 perf:

# perf 比较通用,版本不一样要跟内核完全一致
sudo apt-get install linux-tool-`uname -r`

使用

基本的使用方法:

# 以 99Hz 的频率对整个系统进行采样 10s
sudo perf record -F 99 -ag sleep 10
# 查看采样的结果
sudo perf report

原理

它的基本原理就是通过时钟中断或者事件触发时去查看CPU的状态,获取堆栈的数据。
为了搞清楚它的具体运行情况,我们可以先查看它最早加入内核主线时的代码,也就是 linux-2.6.31 这个版本。(目前 perf 下的代码已经接近 30万行,而最初加入主线时的代码量在 3万行)
首先,perf record 通过 241 号系统调用__NR_perf_counter_open 向内核传递一个 perf counter。这个的系统调用定义在 kernel/perf_counter.c 中,主要的参数就是 struct perf_counter_attr:

SYSCALL_DEFINE5(perf_counter_open,
        struct perf_counter_attr __user *, attr_uptr,
        pid_t, pid, int, cpu, int, group_fd, unsigned long, flags)

内核中 sys_perf_counter_open 这个系统调用会返回给用户一个文件描述符,用于读取 perf 的结果。
对于 perf_counter 涉及到这3个数据类型:struct perf_counterstruct perf_counter_attrstruct perf_counter_context
初始化 perf_counter 之后,会将 perf_counter 挂到需要跟踪的 task 的 perf_counter_list 链表上。
每个 cpu 或者 task 都有一个 perf_counter_context。其中 cpu 的存在 percpu 变量中,而 task 则存在 perf_counter_ctxp 中。
perf_counter 的初始化配置在 perf_counter_alloc 函数中。在这里面根据不同的 counter 类型,初始化了不同的 struct pmu,这是 perf_counter 的4个 method:

/**
 * struct pmu - generic performance monitoring unit
 */
struct pmu {
    int (*enable)           (struct perf_counter *counter);
    void (*disable)         (struct perf_counter *counter);
    void (*read)            (struct perf_counter *counter);
    void (*unthrottle)      (struct perf_counter *counter);
};

perf_counter 的类型主要有下面几种:

enum perf_type_id {
    PERF_TYPE_HARDWARE          = 0,
    PERF_TYPE_SOFTWARE          = 1,
    PERF_TYPE_TRACEPOINT            = 2,
    PERF_TYPE_HW_CACHE          = 3,
    PERF_TYPE_RAW               = 4,

    PERF_TYPE_MAX,              /* non-ABI */
};

schedule 中会调用 perf_counter_task_sched_out 进行计数。
schedule_tick 中会调用 perf_counter_task_tick 进行计数。
perf_callchain 处理调用栈的回溯。
struct hw_perf_counter 貌似是用于硬件定时器的相关参数配置。
__perf_install_in_context 用于安装和启动 perf_counter。couter_sched_in 调用 pmu->enable 启动 perf_counter。
perf_swcounter_hrtimer 为 swcounter 的定时中断服务函数,在这个定时器中断里,通过 pmu->read 更新 perf_counter 的计数值。同时获取当前的寄存器数据,并通过 perf_counter_overflow 采样。
counter_sched_out 调用 pmu->disable 关闭 perf_counter。