Skip to content
Stack & Ink
Go back

【Linux】 perf 机制分析

Linux perf 子系统由两大核心部分构成:用户态 perf 工具用于交互,内核态 perf-event 子系统进行事件管理、调度、采样、缓冲区管理。

Table of contents

Open Table of contents

1. 使用示例

用户态 perf 工具的源码位于 tools/perf ,需要自行编译后放入 rootfs 中。

编写一个用户态程序 perf-test

#include <stdint.h>

__attribute__((noinline))
static void foo(void)
{
    volatile uint64_t x = 0;

    for (uint64_t i = 0; i < 800000000ULL; i++)
        x += i;
}

__attribute__((noinline))
static void bar(void)
{
    volatile uint64_t x = 0;

    for (uint64_t i = 0; i < 200000000ULL; i++)
        x += i;
}

int main(void)
{
    foo();
    bar();
    return 0;
}

使用 perf list 可以看到支持的事件类型,主要有 tracepoint / hardware / software / tool 四类,使用 -e 指定:

perf list
  alarmtimer:alarmtimer_cancel                       [Tracepoint event]
  alarmtimer:alarmtimer_fired                        [Tracepoint event]
  alarmtimer:alarmtimer_start                        [Tracepoint event]
  bpf_test_run:bpf_test_finish                       [Tracepoint event]
  bpf_test_run:bpf_trigger_tp                        [Tracepoint event]
  ......
  
legacy hardware:
  cpu-cycles
       [Total cycles. Be wary of what happens during CPU frequency scaling
        [This event is an alias of cycles]. Unit: armv8_pmuv3]
  cycles
       [Total cycles. Be wary of what happens during CPU frequency scaling
        [This event is an alias of cpu-cycles]. Unit: armv8_pmuv3]
  idle-cycles-backend
       [Stalled cycles during retirement [This event is an alias of
        stalled-cycles-backend]. Unit: armv8_pmuv3]
  idle-cycles-frontend
       [Stalled cycles during issue [This event is an alias of
        stalled-cycles-frontend]. Unit: armv8_pmuv3]
  stalled-cycles-backend
       [Stalled cycles during retirement [This event is an alias of
        idle-cycles-backend]. Unit: armv8_pmuv3]
  stalled-cycles-frontend
       [Stalled cycles during issue [This event is an alias of
        idle-cycles-frontend]. Unit: armv8_pmuv3]

software:
  alignment-faults
       [Number of kernel handled memory alignment faults. Unit: software]
  bpf-output
       [An event used by BPF programs to write to the perf ring buffer. Unit:
        software]
  cgroup-switches
       [Number of context switches to a task in a different cgroup. Unit:
        software]
  context-switches
       [Number of context switches [This event is an alias of cs]. Unit:
        software]
  cpu-clock
       [Per-CPU high-resolution timer based event. Unit: software]
  cpu-migrations
       [Number of times a process has migrated to a new CPU [This event is an
        alias of migrations]. Unit: software]
  cs
       [Number of context switches [This event is an alias of
        context-switches]. Unit: software]
  dummy
       [A placeholder event that doesn't count anything. Unit: software]
  emulation-faults
       [Number of kernel handled unimplemented instruction faults handled
        through emulation. Unit: software]
  faults
       [Number of page faults [This event is an alias of page-faults]. Unit:
        software]
  major-faults
       [Number of major page faults. Major faults require I/O to handle. Unit:
        software]
  migrations
       [Number of times a process has migrated to a new CPU [This event is an
        alias of cpu-migrations]. Unit: software]
  minor-faults
       [Number of minor page faults. Minor faults don't require I/O to handle.
        Unit: software]
  page-faults
       [Number of page faults [This event is an alias of faults]. Unit:
        software]
  task-clock
       [Per-task high-resolution timer based event. Unit: software]

tool:
  core_wide
       [1 if not SMT,if SMT are events being gathered on all SMT threads 1
        otherwise 0. Unit: tool]
  duration_time
       [Wall clock interval time in nanoseconds. Unit: tool]
  has_pmem
       [1 if persistent memory installed otherwise 0. Unit: tool]
  num_cores
       [Number of cores. A core consists of 1 or more thread,with each thread
        being associated with a logical Linux CPU. Unit: tool]
  num_cpus
       [Number of logical Linux CPUs. There may be multiple such CPUs on a
        core. Unit: tool]
  num_cpus_online
       [Number of online logical Linux CPUs. There may be multiple such CPUs
        on a core. Unit: tool]
  num_dies
       [Number of dies. Each die has 1 or more cores. Unit: tool]
  num_packages
       [Number of packages. Each package has 1 or more die. Unit: tool]
  slots
       [Number of functional units that in parallel can execute parts of an
        instruction. Unit: tool]
  smt_on
       [1 if simultaneous multithreading (aka hyperthreading) is enable
        otherwise 0. Unit: tool]
  system_time
       [System/kernel time in nanoseconds. Unit: tool]
  target_cpu
       [1 if CPUs being analyzed,0 if threads/processes. Unit: tool]
  user_time
       [User (non-kernel) time in nanoseconds. Unit: tool]
  rNNN                                               [Raw event descriptor]
  armv8_pmuv3/threshold_count,event=0..0xffff,long,.../modifier[Raw event descriptor]
       [(see 'man perf-list' or 'man perf-record' on how to encode it)]
  breakpoint//modifier                               [Raw event descriptor]
  kprobe/retprobe/modifier                           [Raw event descriptor]
  software//modifier                                 [Raw event descriptor]
  tool//modifier                                     [Raw event descriptor]
  tracepoint//modifier                               [Raw event descriptor]
  uprobe/ref_ctr_offset=0..0xffffffff,retprobe/modifier[Raw event descriptor]
  mem:<addr>[/len][:access]

此外还有 4 组根据多个 events 综合计算得出的 Metric 可以选择,使用 -M 指定:

Metric Groups:

Default:
  CPUs_utilized
       [Average CPU utilization]
  backend_cycles_idle
       [Backend stalls per cycle]
  branch_frequency
       [Branches per CPU second]
  branch_miss_rate
       [Branch miss rate]
  cs_per_second
       [Context switches per CPU second]
  cycles_frequency
       [Cycles per CPU second]
  frontend_cycles_idle
       [Frontend stalls per cycle]
  insn_per_cycle
       [Instructions Per Cycle]
  migrations_per_second
       [Process migrations to a new CPU per CPU second]
  page_faults_per_second
       [Page faults per CPU second]
  stalled_cycles_per_instruction
       [Max front or backend stalls per instruction]

Default2:
  l1d_miss_rate
       [L1D miss rate]
  llc_miss_rate
       [LLC miss rate]

Default3:
  dtlb_miss_rate
       [dTLB miss rate]
  itlb_miss_rate
       [iTLB miss rate]
  l1i_miss_rate
       [L1I miss rate]

Default4:
  l1_prefetch_miss_rate
       [L1 prefetch miss rate]

1.1 stat

计数模式:

perf stat -e cycles,task-clock,sched:sched_switch,duration_time perf-test

 Performance counter stats for 'perf-test':

        2835640627      cycles
           2835.89 msec task-clock
                 2      sched:sched_switch
                 0      duration_time

       2.838354112 seconds time elapsed

       2.830176000 seconds user
       0.007983000 seconds sys

perf stat -M cycles_frequency perf-test

 Performance counter stats for 'perf-test':

        2791872947      cpu-cycles                       #      1.0 GHz  cycles_frequency
           2791.99 msec task-clock

       2.793761216 seconds time elapsed

       2.794174000 seconds user
       0.000000000 seconds sys
      

1.2 record

采样模式,默认输出到perf.data,可以使用 -o 参数指定,-g 开启graph模式,-F 指定采样频率:

perf record -o test.data perf-test

perf report -i test.data
# To display the perf.data header info, please use --header/--header-only options.
#
#
# Total Lost Samples: 0
#
# Samples: 13K of event 'armv8_pmuv3/cycles/P'
# Event count (approx.): 3204211015
#
# Overhead  Command    Shared Object          Symbol
# ........  .........  .....................  .............................
#
    79.99%  perf-test  perf-test              [.] 0x00000000000007e0
    19.88%  perf-test  perf-test              [.] 0x0000000000000824
     0.01%  perf-test  [kernel.kallsyms]      [k] handle_softirqs
     0.01%  perf-test  [kernel.kallsyms]      [k] el0_ia
     0.01%  perf-test  [kernel.kallsyms]      [k] kvfree_call_rcu
     0.01%  perf-test  libc.so.6              [.] 0x000000000015b158
     0.01%  perf-test  [kernel.kallsyms]      [k] perf_iterate_ctx
     0.01%  perf-test  ld-linux-aarch64.so.1  [.] 0x0000000000009480
     0.01%  perf-test  [kernel.kallsyms]      [k] el0_da
     0.01%  perf-test  ld-linux-aarch64.so.1  [.] 0x0000000000020560
     0.01%  perf-test  [kernel.kallsyms]      [k] debug_smp_processor_id
     0.01%  perf-test  [kernel.kallsyms]      [k] do_dentry_open
     0.01%  perf-test  [kernel.kallsyms]      [k] do_mmap
     0.01%  perf-test  ld-linux-aarch64.so.1  [.] 0x00000000000197b8
     0.01%  perf-test  [kernel.kallsyms]      [k] kfree
     0.01%  perf-test  [kernel.kallsyms]      [k] __pi_memset_generic
     0.01%  perf-test  [kernel.kallsyms]      [k] __pi_clear_page
     0.01%  perf-test  [kernel.kallsyms]      [k] check_preemption_disabled
     0.01%  perf-test  [kernel.kallsyms]      [k] __pcs_replace_full_main
     0.01%  perf-test  [kernel.kallsyms]      [k] __free_frozen_pages
     0.00%  perf-test  [kernel.kallsyms]      [k] kmem_cache_free
     0.00%  perf-test  [kernel.kallsyms]      [k] __zap_vma_range
     0.00%  perf-test  [kernel.kallsyms]      [k] __pte_offset_map
     0.00%  perf-test  [kernel.kallsyms]      [k] get_random_u64
     0.00%  perf-test  [kernel.kallsyms]      [k] _raw_spin_lock
     0.00%  perf-exec  [kernel.kallsyms]      [k] sized_strscpy

2. 原理分析

perf-event 将各种类型的事件源都抽象为 PMU。

在 PMU 的结构体定义中包含了一系列回调:

struct pmu {
    int (*event_init)(struct perf_event *event);

    int (*add)(struct perf_event *event, int flags);
    void (*del)(struct perf_event *event, int flags);

    void (*start)(struct perf_event *event, int flags);
    void (*stop)(struct perf_event *event, int flags);

    void (*read)(struct perf_event *event);

    void (*enable)(struct pmu *pmu);
    void (*disable)(struct pmu *pmu);

    ...
};

2.1 初始化 event

当用户态发起一次追踪时:

perf stat -e cycles ./test

实际上是构造了一个 struct perf_event_attr 通过系统调用 perf_event_open 传递给内核:

struct perf_event_attr attr = {
    .type   = PERF_TYPE_HARDWARE,
    .config = PERF_COUNT_HW_CPU_CYCLES,
    ...
};

来到内核后,首先通过 perf_event_alloc 构造一个struct perf_event

perf_event_alloc(...)
{
    event = alloc();

    /*
     * 1. 初始化 perf core 通用状态
     */
    event->attr  = *attr;
    event->cpu   = cpu;
    event->state = PERF_EVENT_STATE_INACTIVE;

    /*
     * 2. 初始化 hw_perf_event 通用部分
     */
    hwc = &event->hw;
    hwc->sample_period = attr->sample_period;
    ...

    /*
     * 3. 选择 PMU + 调用 pmu->event_init()
     */
    pmu = perf_init_event(event);

    /*
     * 此时:
     *
     * event->pmu = pmu
     * event->hw 已被具体 PMU 初始化
     */

    /*
     * 4. 初始化各种附加资源
     */
    ...

    /*
     * 5. 挂到 PMU 的 event 总表
     */
    list_add(&event->pmu_list, &pmu->events);

    return event;
}

接下来会将 event 安装到某个 task 的 perf_event_ctxp 字段上

static void perf_install_in_context(struct perf_event_context *ctx,
                                    struct perf_event *event,
                                    int cpu)
{
    struct task_struct *task = ctx->task;

    event->ctx = ctx;

    /*
     * 1. disabled event,而且 context 里已经有 event:
     *    不需要立刻打 IPI 去重编程硬件,直接挂进去。
     */
    if (event_is_off(event) && ctx->nr_events) {
        lock(ctx);
        add_event_to_ctx(event, ctx);
        unlock(ctx);
        return;
    }

    /*
     * 2. per-CPU event:
     *    必须让目标 CPU 自己执行安装。
     */
    if (!task) {
        cpu_function_call(cpu, __perf_install_in_context, event);
        return;
    }

    /*
     * 3. per-task event:
     *    如果 task 正在某个 CPU 上运行,
     *    让那个 CPU 执行 __perf_install_in_context()。
     */
    if (task_is_running(task)) {
        task_function_call(task, __perf_install_in_context, event);
        return;
    }

    /*
     * 4. task 当前没运行:
     *    直接把 event 挂到 ctx。
     *    以后 task schedule in 时再把 event 放进 PMU。
     */
    lock(ctx);
    add_event_to_ctx(event, ctx);
    unlock(ctx);
}

2.2 开启 / 停止计数

当挂载了 perf_event_ctxp 的 task 被调度执行时会来到:

static int event_sched_in(struct perf_event *event,
                          struct perf_event_context *ctx)
{
    /*
     * OFF/ERROR 状态的 event 不能被调度到 PMU 上。
     * 只有 INACTIVE event 才有资格变成 ACTIVE。
     */
    if (event->state <= PERF_EVENT_STATE_OFF)
        return 0;

    /*
     * 记录这个 event 当前运行在哪个 CPU,
     * 并先标记为 ACTIVE。
     */
    event->oncpu = smp_processor_id();
    event->state = PERF_EVENT_STATE_ACTIVE;

    /*
     * 修改 PMU 配置前,先临时暂停整个 PMU。
     */
    perf_pmu_disable(event->pmu);

    /*
     * 真正把 event 加到 PMU。
     *
     * PERF_EF_START 表示:
     *   add 成功后直接启动 counter。
     *
     * 对硬件 PMU 来说,这里通常会:
     *   1. 分配硬件 counter
     *   2. 设置 event->hw.idx
     *   3. 配置硬件事件
     *   4. 启动 counter
     */
    if (event->pmu->add(event, PERF_EF_START)) {

        /*
         * PMU 资源不足或 add 失败,
         * 回滚到“已 enable 但没在硬件上运行”的状态。
         */
        event->state = PERF_EVENT_STATE_INACTIVE;
        event->oncpu = -1;

        perf_pmu_enable(event->pmu);
        return -EAGAIN;
    }

    /*
     * PMU 配置完成,恢复整个 PMU。
     */
    perf_pmu_enable(event->pmu);

    return 0;
}

当 task 被调度出去时会执行:

static void event_sched_out(struct perf_event *event)
{
    /*
     * 当前 event 已经不再运行在 CPU 的 PMU 上。
     */
    event->state = PERF_EVENT_STATE_INACTIVE;
    event->oncpu = -1;

    /*
     * 修改 PMU 前先暂停整个 PMU。
     */
    perf_pmu_disable(event->pmu);

    /*
     * 从 PMU 移除 event。
     *
     * 对硬件 PMU 来说,del() 通常会:
     *   1. stop counter
     *   2. 更新 event->count
     *   3. 释放硬件 counter
     */
    event->pmu->del(event, 0);

    /*
     * PMU 配置完成后恢复 PMU。
     */
    perf_pmu_enable(event->pmu);
}

2.3 获取结果

计数模式:事件发生后累计到 event->count,用户态通过 read(perf_event_fd) 读取;如果 event 仍是 ACTIVE,内核会先通过 pmu->read() 把最新硬件值同步到 event->count

采样模式

2.4 多路复用

硬件 PMU 的多路复用,就是当需要监控的硬件事件数量超过可用 PMU counter 数量时,perf 会让不同 event 分批、轮流占用这些硬件 counter:当前被装入 PMU 的 event 处于 ACTIVE,其余保持 INACTIVE,轮换时通过 event_sched_out() -> pmu->del()/stop() 保存计数,再通过 event_sched_in() -> pmu->add()/start() 换入下一批;同时 perf 为每个 event 记录 time_enabledtime_running,最后按实际运行时间比例对计数值进行 scaling,从而用有限的硬件 counter 近似同时监控更多事件。


Share this post:

Next Post
【Linux】 ftrace 机制分析