Linux 性能检测调优之Perf

xiaoxiao2021-02-28  41

引言

Linux性能检测/调优是一个很大的话题,涉及的内容也非常广泛。目前,从硬件层面到系统层面都提供了一系列硬件支持/软件工具给开发者来检测系统性能以针对具体问题进行调优。

本文主要关注其中的利用性能计数器(Performance Counter)进行性能检测的linux工具Perf,介绍了其强大的能力以及部分使用方法。本文主要部分参考了Brendan D. Gregg的博客,在该博客的Perf Examples分栏下有详细的每个Perf的使用方法,可以进一步详细查看。同博客中的叫法相似,这里对Perf利用Perf_Events来指代,避免产生异义。

Perf_Events概述

Perf Event是面向事件的观察工具。简要来说,Perf_Events可以用于解决下面的问题:

为什么内核占用这么多CPU时间?具体是哪一个代码段耗时?哪部分代码导致CPU会导致L2的缓存失效?是否CPU被memory I/O所害?哪段代码在疯狂分配内存?到底谁导致了TCP的重传?是否内核中的某一个方法被调用了,有多频繁?

一共有两种Perf_Events:第一种是直接利用现有提供的Events,其覆盖了大部分可能会用到的事件。而如果刚好里面没有想要的,则需要利用第二种方式,自己写新的Perf_Events。

而针对如何如何利用Perf_Events进行测量,一共有三种方法:

直接利用Counting Event计数,可以利用perf工具直接对发生的次数进行计数。这种方法不会生成perf.data文件,直接利用perf stat命令即可。在指定的时间进行取样,使用这种方法会将Perf_Events数据写到内核缓存里面,然后再由Perf隔一段时间写入perf.data文件中。最后利用perf report或者perf script读取。但是利用这种方法进行采样,report文件的大小overhead比较高(文件很大)。最后一种方法是利用BPF触发用户自己写的程序,这种方法最灵活。但是同时这种方法也比较复杂,需要自己写触发程序,在后面的eBPF章节中进行描述。

Perf_Event的种类与使用方法

如上图所示,Linux Perf_Events可以分为以下几类:

硬件事件 Hardware Events: CPU 性能检测计数器软件事件 Software Events: 利用kernel counters低层次的events,例如CPU迁移, minor faults, major faults.内核追踪事件 Kernel Tracepoint Events: 有些内核态的tracepoint被硬件编码到内核中的一些地方。用户态静态追踪事件 User Statically-Defined Tracing (USDT): 用户态程序中的静态tracepoint。动态追踪 Dynamic Tracing: 利用kprobe和uprobe在任意位置创建event。Timed Profiling: 可以间隔一段时间时间进行快照。

目前perf支持的事件可以用perf list列举出来。而如果使用动态追踪,可以追踪其他的事件。下面针对上面几种事件进行详细分析。

硬件事件 Hardware Events

什么是Hardware Events

硬件事件是利用处理器的performance Monitoring Unit(PMU)实现。读取其中的Performace Monitoring Counters(PMCs)或者称为Performance instrumentation counters(PICs)。这些Counter可以跟踪一些底层的动作,如CPU cycles,instructions retired, memory stall cycles, level 2 cache misses等等。

这些硬件事件的特点是只有其中少数几个事件可以同时被记录。 这时因为硬件资源有限,需要手动指定它们记录哪些event。

如何使用Hardware Events

使用硬件Raw Counter的格式是rUUEE,其中UU是umask,EE是event number。而大部分好用的直接加到了perf list中,直接用对应的事件就行。

如果需要做stack tracing,避免记录的overhead太大,可以指定没n次做一个trace,直接设定-c n即可。如:perf record -e L1-dcache-load-misses -c 10000 -ag -- sleep 5

Intel PEBS Precise Event-Based Sampling (AMD IBS)

为了获取更加精确的结果,可以使用PEBS。PEBS利用硬件支持,获取CPU真实的状态。并不是所有的PMC都支持PEBS。如果支持的话,直接在对应的事件名后面加上:p即可。

内核追踪事件 Kernel Tracepoints

什么是Kernel Tracepoints

Tracepoints可以理解为类似代码中插入#ifdef DEBUG,在指定时可以在固定点运行指定的代码。

内核的Tracepoints是在内核代码中有意思的地方或者逻辑上分割的地方编写插入,因此用到这些的高层的事件能够被追踪。比如 system calls、TCP events、file system I/O和disk I/O等等。它们被划分很多组,如sock:代表着socket events,而sched:代表着CPU scheduler events。

这些events都是在include/trace/events/*中

#root@ts850:/sys/kernel/debug/tracing/events# ls block fib6 irq_vectors napi rcu task cgroup filelock jbd2 net regmap thermal clk filemap kmem nmi rpm timer compaction ftrace kvm oom sched tlb cpuhp gpio kvmmmu page_isolation scsi udp drm header_event libata pagemap signal vmscan enable header_page mce power skb vsyscall exceptions huge_memory migrate printk sock workqueue ext4 i2c module random spi writeback fence iommu mpx ras swiotlb x86_fpu fib irq msr raw_syscalls syscalls xen

如何去使用Hardware Events

下面是一个使用的实际例子,尝试获取所有的系统调用:

$ sudo perf stat -e 'syscalls:sys_enter_*' ls 2>&1 | awk '$1 != 0' project testspace tools Performance counter stats for 'ls': 2 syscalls:sys_enter_statfs 2 syscalls:sys_enter_getdents 2 syscalls:sys_enter_ioctl 10 syscalls:sys_enter_newfstat 7 syscalls:sys_enter_read 1 syscalls:sys_enter_write 8 syscalls:sys_enter_access 9 syscalls:sys_enter_open 11 syscalls:sys_enter_close 12 syscalls:sys_enter_mprotect 3 syscalls:sys_enter_brk 1 syscalls:sys_enter_munmap 1 syscalls:sys_enter_set_robust_list 1 syscalls:sys_enter_getrlimit 1 syscalls:sys_enter_rt_sigprocmask 2 syscalls:sys_enter_rt_sigaction 1 syscalls:sys_enter_exit_group 1 syscalls:sys_enter_set_tid_address 17 syscalls:sys_enter_mmap 0.002902927 seconds time elapsed

这个功能和strace实现的类似,但是不同的是strace会比perf拥有更大的overhead,这时因为perf的buffer是在内核的。

用户态静态追踪事件 User-Level Statically Defined Tracing

什么是User-Level Statically Defined Tracing

与kernel tracepoints类似,USDT是在用户层应用程序中放入这些tracepoint。很多应用程序需要手动编译时选择编译带dtrace版本。最后可以用readily -n node来都出来这些probes。

如何去使用

先需要将对应的symbol加入到perf中

# perf buildid-cache --add `which node` # perf list | grep sdt_node sdt_node:gc__done [SDT event] sdt_node:gc__start [SDT event] sdt_node:http__client__request [SDT event] sdt_node:http__client__response [SDT event] sdt_node:http__server__request [SDT event] sdt_node:http__server__response [SDT event] sdt_node:net__server__connection [SDT event] sdt_node:net__stream__end [SDT event] # perf record -e sdt_node:http__server__request -a ^C[ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.446 MB perf.data (3 samples) ] # perf script node 7646 [002] 361.012364: sdt_node:http__server__request: (dc2e69) node 7646 [002] 361.204718: sdt_node:http__server__request: (dc2e69) node 7646 [002] 361.363043: sdt_node:http__server__request: (dc2e69)

动态追踪 Dynamic Tracing

什么是Dynamic Tracing

与Static Tracepoints的区别如下图所示:

虽然动态追踪可以追踪任何东西,但不是很稳定。可能会在kernel patch或者update之后会无法使用。所以先要尝试用用静态的追踪,再看是不是要用动态的。但是Dynamic Tracing可以无需重启任何东西就用。

如何去使用Dynamic Tracing

首先添加新的probe

$ perf probe --add tcp_sendmsg Added new event: probe:tcp_sendmsg (on tcp_sendmsg) You can now use it in all perf tools, such as: perf record -e probe:tcp_sendmsg -aR sleep 1

还可以类似以下,指定某一行的某一个变量:

# perf probe --add 'tcp_sendmsg:81 seglen' Added new event: probe:tcp_sendmsg (on tcp_sendmsg:81 with seglen) You can now use it in all perf tools, such as: perf record -e probe:tcp_sendmsg -aR sleep

如果需要引用其他库里面的# perf probe -x /lib/x86_64-linux-gnu/libc-2.15.so --add malloc

使用probe:标识跟踪:

$ sudo perf record -e probe:tcp_sendmsg -a -g -- sleep 5 [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.204 MB perf.data (4 samples) ] $ sudo perf report --stdio 100.00% 100.00% (ffffffff8bf52550) | ---0 __GI___libc_write return_from_SYSCALL_64 do_syscall_64 sys_write vfs_write new_sync_write sock_write_iter sock_sendmsg tcp_sendmsg

使用—del可以删除。

还可以看具体的变量数据,使用-V标记即可。

Timed Profiling

可以要求perf工具间隔固定时间进行采样。如-F 99,最后结果可以生成Flame Graphs便于查找问题。

使用BPF触发自己写的程序

使用BPF可以更加灵活地指定或筛选自己需要用到的内容。利用eBPF的方法简要描述如下,具体的可以参考博客中的eBPF章节:

在/proc/kallsyms中找到想要追踪的地址。

写自己的BPF程序:

int func(struct pt_regs *ctx) { u64 ret = 0; // x86_64 specific: probe_read(&ret, sizeof(ret), (void *)(ctx->bp+8)); if (ret >= RANGE_START && ret < RANGE_END) { perf_event_output(ctx, &channel, get_smp_processor_id(), &ret, sizeof(ret)); } return 0; }

直接用perf指定用自己的程序进行trace # perf record -e bpf-output/no-inherit,name=evt/ -e ./kca_from.c/map:channel.event=evt/ -a -- sleep 1

转载请注明原文地址: https://www.6miu.com/read-2619147.html

最新回复(0)