您当前的位置:首页 > 电脑百科 > 软件技术 > 操作系统 > linux百科

linux性能工具perf工作原理简析

时间:2019-09-20 09:28:29  来源:  作者:
地址:https://my.oschina.NET/u/2475751/blog/1823736

背景

此前工作中,笔者使用perf测过CPU的CPI[1],cache miss, 内存带宽等性能指标。另外,还移植过perf uncore[2]相关的补丁。这些让我很好奇:perf大概是怎么工作的? 带着这个问题,笔者谨希望把自己的一点经验分享出来。

perf-list

perf list列出的event有这几类:1. hardware,如cache-misses; 2. software, 如context switches; 3. cache, 如L1-dcache-loads;4. tracepoint; 5. pmu。 但是,perf list仅仅把有符号名称的事件列出来了,而缺了很多硬件相关的事件。这些硬件相关事件叫作Raw Hardware Event, man perf-list有介绍。

举个例子,PMU是一组监控CPU各种性能的硬件,包括各种core, offcore和uncore事件。单说perf uncore, Intel处理器就提供了各种的性能监控单元,如内存控制器(IMC), 电源控制(PCU)等等,详见《Intel® Xeon® Processor E5 and E7 v4 Product Families Uncore Performance Monitoring Reference Manual》[3]。这些uncore的PMU设备,注册在MSR space或PCICFG space[4],可以通过下面命令看到(抹掉同类别设备):

$ls /sys/devices/ | grep uncore
uncore_cbox_0
uncore_ha_0
uncore_imc_0
uncore_pcu
uncore_qpi_0
uncore_r2pcie
uncore_r3qpi_0
uncore_ubox

但是,使用perf list只能显示IMC相关事件:

$perf list|grep uncore
 uncore_imc_0/cas_count_read/ [Kernel PMU event]
 uncore_imc_0/cas_count_write/ [Kernel PMU event]
 uncore_imc_0/clockticks/ [Kernel PMU event]
 ... 
 uncore_imc_3/cas_count_read/ [Kernel PMU event]
 uncore_imc_3/cas_count_write/ [Kernel PMU event]
 uncore_imc_3/clockticks/ [Kernel PMU event]

为什么perf list没有显示其他uncore事件呢?从代码分析来看,perf list会通过sysfs去读取uncore设备所支持的event, 见linux/tools/perf/util/pmu.c:pmu_aliases():

/*
 * Reading the pmu event aliases definition, which should be located at:
 * /sys/bus/event_source/devices/<dev>/events as sysfs group attributes.
 */
 static int pmu_aliases(const char *name, struct list_head *head)

再看perf uncore的驱动代码,发现只有iMC uncore设备注册了events相关属性, 见arch/x86/events/intel/uncore_snbep.c:hswep_uncore_imc_events:

static struct uncore_event_desc hswep_uncore_imc_events[] = {
 INTEL_UNCORE_EVENT_DESC(clockticks, "event=0x00,umask=0x00"),
 INTEL_UNCORE_EVENT_DESC(cas_count_read, "event=0x04,umask=0x03"),
 INTEL_UNCORE_EVENT_DESC(cas_count_read.scale, "6.103515625e-5"),
 INTEL_UNCORE_EVENT_DESC(cas_count_read.unit, "MiB"),
 INTEL_UNCORE_EVENT_DESC(cas_count_write, "event=0x04,umask=0x0c"),
 INTEL_UNCORE_EVENT_DESC(cas_count_write.scale, "6.103515625e-5"),
 INTEL_UNCORE_EVENT_DESC(cas_count_write.unit, "MiB"),
 { /* end: all zeroes */ },
};

从实用性看,在所有uncore设备中,系统工程师可能最常用的就是iMC提供的内存带宽监测。其它不常用到的uncore PMU事件,可以通过Raw Hardware Event的方式,查看Intel Uncore手册[5]来指定。

在使用过程中,发现一个perf list存在的bug,iMC channel的编号不正确,发了个补丁得到了Intel工程师review,upstream还没有merge,见perf/x86/intel/uncore: allocate pmu index for pci device dynamically[6]。这是一个很明显的问题,刚开始我不相信上游或Intel会允许这样明显的问题存在,虽然问题不大,通过解决这个问题的感受是perf可能隐藏一些问题,需要在测试中提高警惕,最好能通过其他测量方式进行粗略的对比验证。

perf-stat

perf-stat是最常用到的命令,用man手册的原话就是Run a command and gathers performance counter statistics from it。perf-record命令可看做是perf-stat的一种包装,核心代码路径与perf-stat一样,加上周期性采样,用一种可被perf-report解析的格式将结果输出到文件。因此,很好奇perf-stat是如何工作的。

perf是由用户态的perf tool命令和内核态perf驱动两部分,加上一个连通用户态和内核态的系统调用sys_perf_event_open组成。

最简单的perf stat示例

perf工具是随内核tree一起维护的,构建和调试都非常方便:

$cd linux/tools/perf
$make
...
$./perf stat ls
...
 Performance counter stats for 'ls':
 1.011337 task-clock:u (msec) # 0.769 CPUs utilized
 0 context-switches:u # 0.000 K/sec
 0 cpu-migrations:u # 0.000 K/sec
 105 page-faults:u # 0.104 M/sec
 1,105,427 cycles:u # 1.093 GHz
 1,406,263 instructions:u # 1.27 insn per cycle
 282,440 branches:u # 279.274 M/sec
 9,686 branch-misses:u # 3.43% of all branches
 0.001314310 seconds time elapsed

以上是一个非常简单的perf-stat命令,运行了ls命令,在没有指定event的情况下,输出了几种默认的性能指标。下面,我们以这个简单的perf-stat命令为例分析其工作过程。

用户态工作流

如果perf-stat命令没有通过-e参数指定任何event,函数add_default_attributes()会默认添加8个events。 event是perf工具的核心对象,各种命令都是围绕着event工作。perf-stat命令可以同时指定多个events,由一个核心全局变量struct perf_evlist *evsel_list组织起来,以下仅列出几个很重要的成员:

struct perf_evlist {
 struct list_head entries;
 bool enabled;
 struct {
 int cork_fd;
 pid_t pid;
 } workload;
 struct fdarray pollfd;
 struct thread_map *threads;
 struct cpu_map *cpus;
 struct events_stats stats;
 ...
} 
  • entries: 所有events列表, 即struct perf_evsel对象;
  • pid: 运行cmd的进程pid, 即运行ls命令的进程pid;
  • pollfd: 保存sys_perf_event_open()返回的fd;
  • threads: perf-stat可以通过-t参数指定多个线程,仅在这些线程运行时进行计数;
  • cpus: perf-stat能通过-C参数指定多个cpu, 仅当程序运行在这些cpu上时才会计数;
  • stats: 计数统计结果,perf-stat从mmap内存区读取counter值后,还要做一些数值转换或聚合等处理

perf_evlist::entries是一个event链表,链接的对象是一个个event,由struct perf_evsel表示,其中非常重要的成员如下:

struct perf_evsel {
char *name;
struct perf_event_attr attr;
struct perf_counts *counts;
struct xyarray *fd;
struct cpu_map *cpus;
struct thread_map *threads;
}
  • name: event的名称;
  • attr: event的属性,传递给perf系统调用非常重要的参数;
  • cpus, threads, fd: perf-stat可以指定一些对event计数的限制条件,只统计哪些task或哪些cpu, 其实就是一个由struct xyarray表示的二维表格,最终的计数值被分解成cpus*threads个小的counter,sys_perf_event_open()请求perf驱动为每个分量值创建一个子counter,并分别返回一个fd;
  • counts: perf_counts::values保存每个分量计数值,perf_counts::aggr保存最终所有分量的聚合值。

perf的性能计数器本质上是一些特殊的硬件寄存器,perf对这样的硬件能力进行抽象,提供针对event的per-CPU和per-thread的64位虚机计数器("virtual" 64-bit counters)。当perf-stat不指定任何thread或cpu时,这样的一个二维表格就变成一个点,即一个event对应一个counter,对应一个fd。

简单介绍了核心数据结构,终于可以继续看看perf-stat的工作流了。perf-stat的工作逻辑主要在__run_perf_stat()中,大致是这样: a. fork一个子进程,准备用来运行cmd,即示例中的ls命令;b. 为每一个event事件,通过sys_perf_event_open()系统调用,创建一个counter; c. 通过管道给子进程发消息,exec命令, 即运行示例中的ls命令, 并立即enable计数器; d. 当程序运行结束后,disable计数器,并读取counter。 用户态的工作流大致如下:

__run_perf_stat()
 perf_evlist__prepare_workload()
 create_perf_stat_counter()
 sys_perf_event_open()
 enable_counters()
 perf_evsel__run_ioctl(evsel, ncpus, nthreads, PERF_EVENT_IOC_DISABLE)
 ioctl(fd, ioc, arg)
 wAIt()
 disable_counters()
 perf_evsel__run_ioctl(evsel, ncpus, nthreads, PERF_EVENT_IOC_ENABLE)
 read_counters()
 perf_evsel__read(evsel, cpu, thread, count)
 readn(fd, count, size)

用户态工作流比较清晰,最终都可以很方便通过ioctl()控制计数器,通过read()读取计数器的值。而这样方便的条件都是perf系统调sys_perf_event_open()用创造出来的,已经迫不及待想看看这个系统调用做了些什么。

perf系统调用

perf系统调用会为一个虚机计数器(virtual counter)打开一个fd,然后perf-stat就通过这个fd向perf内核驱动发请求。perf系统调用定义如下(linux/kernel/events/core.c):

/**
 * sys_perf_event_open - open a performance event, associate it to a task/cpu
 *
 * @attr_uptr: event_id type attributes for monitoring/sampling
 * @pid: target pid
 * @cpu: target cpu
 * @group_fd: group leader event fd
 */
SYSCALL_DEFINE5(perf_event_open,
 struct perf_event_attr __user *, attr_uptr,
 pid_t, pid, int, cpu, int, group_fd, unsigned long, flags)

特别提一下, struct perf_event_attr是一个信息量很大的结构体,kernel中有文档详细介绍[7]。其它参数如何使用,man手册有详细的解释,并且手册最后还给出了用户态编程例子,见man perf_event_open。

sys_perf_event_open()主要做了这几件事情:

a. 根据struct perf_event_attr,创建和初始化struct perf_event, 它包含几个重要的成员:

/**
 * struct perf_event - performance event kernel representation:
 */
struct perf_event {
	struct pmu *pmu; //硬件pmu抽象
	local64_t count; // 64-bit virtual counter
	u64 total_time_enabled;
	u64 total_time_running;
	struct perf_event_context *ctx; // 与task相关
...
}

b. 为这个event找到或创建一个struct perf_event_context, context和event是1:N的关系,一个context会与一个进程的task_struct关联,perf_event_count::event_list表示所有对这个进程感兴趣的事件, 它包括几个重要成员:

struct perf_event_context {
 struct pmu *pmu;
 struct list_head event_list;
 struct task_struct *task;
 ...
}

c. 把event与一个context进行关联,见perf_install_in_context();

d. 最后,把fd和perf_fops进行绑定:

static const struct file_operations perf_fops = {
 .llseek = no_llseek,
 .release = perf_release,
 .read = perf_read,
 .poll = perf_poll,
 .unlocked_ioctl = perf_ioctl,
 .compat_ioctl = perf_compat_ioctl,
 .mmap = perf_mmap,
 .fasync = perf_fasync,
};

perf系统调用大致的调用链如下:

sys_perf_event_open()
	get_unused_fd_flags()
 	perf_event_alloc()
 	find_get_context()
 		alloc_perf_context()
 	anon_inode_getfile()
 	perf_install_in_context()
 		add_event_to_ctx()
 	fd_install(event_fd, event_file)

内核态工作流

perf event有两种方式:计数(counting)和采样(sampled)。计数方式会对发生在所有指定cpu和指定进程的事件次数进行求和,对事件数值通过read()获得。而采样方式会周期性地把计数结果放在由mmap()创建的ring buffer中。回到开始的简单perf-stat示例,用的是计数(counting)方式。

接下来,我们主要了解这几个问题:

  1. 怎么enable和disable计数器?
  2. 进行计数的时机在哪里?
  3. 如何读取计数结果?

回答这些问题的入口,基本都在perf实现的文件操作集中:

static const struct file_operations perf_fops = {
 .read = perf_read,
 .unlocked_ioctl = perf_ioctl,
...

首先,我们看一下怎样enable计数器的,主要步骤如下:

perf_ioctl()
	__perf_event_enable()
		ctx_sched_out() IF ctx->is_active
		ctx_resched()
			perf_pmu_disable()
			task_ctx_sched_out()
			cpu_ctx_sched_out()
			perf_event_sched_in()
				event_sched_in()
					event->pmu->add(event, PERF_EF_START)
			perf_pmu_enable()
				pmu->pmu_enable(pmu)

这个过程有很多调度相关的处理,使整个逻辑显得复杂,我们暂且不关心太多调度细节。硬件的PMU资源是有限的,当event数量多于可用的PMC时,多个virtual counter就会复用硬件PMC。因此, PMU先把event添加到激活列表(pmu->add(event, PERF_EF_START)), 最后enable计数(pmu->pmu_enable(pmu) )。PMU是CPU体系结构相关的,可以想象它有一套为event分配具体硬件PMC的逻辑,我们暂不深究。

我们继续了解如何获取计数器结果,大致的callchain如下:

perf_read()
	perf_read_one()
		perf_event_read_value()
			__perf_event_read()
				pmu->start_txn(pmu, PERF_PMU_TXN_READ)
				pmu->read(event)
				pmu->commit_txn(pmu)

PMU最终会通过rdpmcl(counter, val)获得计数器的值,保存在perf_event::count中。关于PMU各种操作说明,可以参考include/linux/perf_event.h:struct pmu{}。PMU操作的实现是体系结构相关的,x86上的read()的实现是arch/x86/events/core.c:x86_pmu_read()。

event可以设置限定条件,仅当指定的进程运行在指定的cpu上时,才能进行计数,这就是上面提到的计数时机问题。很容易想到,这样的时机发生在进程切换的时候。当目标进程切换出目标CPU时,PMU停止计数,并将硬件寄保存在内存变量中,反之亦然,这个过程类似进程切换时对硬件上下文的保护。在kernel/sched/core.c, 我们能看到这些计数时机。

在进程切换前:

prepare_task_switch()
	perf_event_task_sched_out()
		__perf_event_task_sched_out() // stop each event and update the event value in event->count
			perf_pmu_sched_task()
				pmu->sched_task(cpuctx->task_ctx, sched_in)

进程切换后:

finish_task_switch()
	perf_event_task_sched_in()
		perf_event_context_sched_in()
			perf_event_sched_in()

小结

通过对perf-list和perf-stat这两个基本的perf命令进行分析,引出了一些有意思的问题,在尝试回答这些问题的过程中,基本上总结了目前我对perf这个工具的认识。但是,本文仅对perf的工作原理做了很粗略的梳理,也没有展开对PMU层,perf uncore等硬件相关代码进行分析,希望以后能补上这部分内容。

最后,能坚持看到最后的亲们都是希望更深了解性能测试的,作为福利给大家推荐本书:《system performance: enterprise and the cloud》(https://pan.baidu.com/s/1yyPsJxi0XWSwIKOrAWm-Vg?errno=0&errmsg=Auth%20Login%20Sucess&&bduss=&ssnerror=0&traceid=) 书的作者是一位从事多年性能优化工作的一线工程师,想必大家都听说过他写的火焰图程序: perf Examples【http://www.brendangregg.com/perf.html

Cheers!

参考索引

  1. Cycles per instruction: https://en.wikipedia.org/wiki/Cycles_per_instruction
  2. uncore: https://en.wikipedia.org/wiki/Uncore
  3. 《Intel® Xeon® Processor E5 and E7 v4 Product Families Uncore Performance Monitoring Reference Manual》
  4. 《Linux设备驱动程序》中第二章PCI驱动程序
  5. https://patchwork.kernel.org/patch/10412883/
  6. linux/tools/perf/design.txt


Tags:linux perf   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
linux性能工具perf工作原理简析
此前工作中,笔者使用perf测过CPU的CPI[1],cache miss, 内存带宽等性能指标。另外,还移植过perf uncore[2]相关的补丁。这些让我很好奇:perf大概是怎么工作的? 带着这个问题,笔者谨希望把自己的一点经验分享出来。...【详细内容】
2019-09-20  Search: linux perf  点击:(1700)  评论:(0)  加入收藏
▌简易百科推荐
微软 Win11 Linux 子系统(WSL)发布 2.2.2 版本
IT之家 4 月 8 日消息,微软近日更新 Windows Subsystem for Linux(WSL),最新 2.2.2 版本中带来了诸多改进,重点更新了 nft 规则,可以让 IPv6 流量通过 Linux 容器。图源: dev.to,AI...【详细内容】
2024-04-08    IT之家  Tags:Linux   点击:(9)  评论:(0)  加入收藏
从原理到实践:深入探索Linux安全机制
Linux 是一种开源的类Unix操作系统内核,由Linus Torvalds在1991年首次发布,其后又衍生出许多不同的发行版(如Ubuntu、Debian、CentOS等)。前言本文将从用户和权限管理、文件系统...【详细内容】
2024-03-27  凡夫编程  微信公众号  Tags:Linux安全   点击:(24)  评论:(0)  加入收藏
在Linux系统中,如何处理内存管理和优化的问题?
本文对 Linux 内存管理和优化的一些高级技巧的详细介绍,通过高级的内存管理技巧,可以帮助系统管理员和开发人员更好地优化 Linux 系统的内存使用情况,提高系统性能和稳定性。在...【详细内容】
2024-03-26  编程技术汇  微信公众号  Tags:Linux   点击:(16)  评论:(0)  加入收藏
Linux 6.9-rc1 内核发布:AMD P-State 首选核心、BH 工作队列
IT之家 3 月 25 日消息,Linus Torvalds 宣布,Linux 6.9 内核的首个 RC(候选发布)版 Linux 6.9-rc1 发布。▲ Linux 6.9-rc1Linus 表示,Linux 内核 6.9 看起来是一个“相当正常”...【详细内容】
2024-03-25    IT之家  Tags:Linux   点击:(15)  评论:(0)  加入收藏
轻松实现Centos系统的软件包安装管理:yum指令实战详解
yum 是一种用于在 CentOS、Red Hat Enterprise Linux (RHEL) 等基于 RPM 的 Linux 发行版上安装、更新和管理软件包的命令行工具。它可以自动解决软件包依赖关系,自动下载并...【详细内容】
2024-02-27  凡夫贬夫  微信公众号  Tags:Centos   点击:(59)  评论:(0)  加入收藏
Win + Ubuntu 缝合怪:第三方开发者推出“Wubuntu”Linux 发行版
IT之家 2 月 26 日消息,一位第三方开发者推出了一款名为“Wubuntu”的缝合怪 Linux 发行版,系统本身基于 Ubuntu,但界面为微软 Windows 11 风格,甚至存在微软 Windows 徽标。据...【详细内容】
2024-02-27    IT之家  Tags:Ubuntu   点击:(53)  评论:(0)  加入收藏
Linux中磁盘和文件系统工作原理解析
在Linux系统中,一切皆文件的概念意味着所有的资源,包括普通文件、目录以及设备文件等,都以文件的形式存在。这种统一的文件系统管理方式使得Linux系统具有高度的灵活性和可扩展...【详细内容】
2024-02-20  王建立    Tags:Linux   点击:(58)  评论:(0)  加入收藏
Linux子系统概览
inux操作系统是一个模块化的系统,由多个子系统组成。这些子系统协同工作,使Linux能够执行各种任务。了解Linux的子系统有助于更好地理解整个操作系统的运作机制。以下是Linux...【详细内容】
2024-02-01    简易百科  Tags:Linux   点击:(85)  评论:(0)  加入收藏
Linux内核:系统之魂与交互之源
内核,作为任何基于Linux的操作系统的心脏,扮演着至关重要的角色。它不仅是计算机系统软件与硬件之间的桥梁,更是确保系统稳定、高效运行的关键。内核提供了一系列核心功能,为上...【详细内容】
2024-02-01  松鼠宝贝    Tags:Linux内核   点击:(72)  评论:(0)  加入收藏
如何确保Linux进程稳定与持久
在Linux系统中,进程的稳定性与持久性对于维持系统的持续运行至关重要。然而,由于各种原因,进程可能会面临崩溃或系统重启的情况。为了确保关键进程能够持续运行,我们必须采取一...【详细内容】
2024-01-19  松鼠宝贝    Tags:Linux进程   点击:(90)  评论:(0)  加入收藏
站内最新
站内热门
站内头条