Linux性能分析工具Perf简介

戈壁老王
本文为翻译,英语好的同学请阅读原文。

介绍

Perf是一个基于Linux 2.6 +系统的分析工具,它抽象了在Linux中性能度量中CPU的硬件差异 ,提供一个简单的命令行界面。 Perf基于最新版本Linux内核 的perf_events 接口。 这篇文章通过示例展示了 Perf工具的使用。 输出结果在Ubuntu 11.04(内核版本2.6.38-8-generic)上获得,硬件是在使用双核英特尔Core2 T7100 CPU的惠普6710 b。 为了可读性,一些输出使用省略号( […] )。

命令

Perf工具提供了一组丰富的命令来收集和分析性能和跟踪数据。 命令行的用法与git 类似,通过一个通用的命令Perf,实现了一组子命令: stat , record , report ,[…]

支持的命令列表:

perf

 usage: perf [--version] [--help] COMMAND [ARGS]

 The most commonly used perf commands are:
  annotate        Read perf.data (created by perf record) and display annotated code
  archive         Create archive with object files with build-ids found in perf.data file
  bench           General framework for benchmark suites
  buildid-cache   Manage <tt>build-id</tt> cache.
  buildid-list    List the buildids in a perf.data file
  diff            Read two perf.data files and display the differential profile
  inject          Filter to augment the events stream with additional information
  kmem            Tool to trace/measure kernel memory(slab) properties
  kvm             Tool to trace/measure kvm guest os
  list            List all symbolic event types
  lock            Analyze lock events
  probe           Define new dynamic tracepoints
  record          Run a command and record its profile into perf.data
  report          Read perf.data (created by perf record) and display the profile
  sched           Tool to trace/measure scheduler properties (latencies)
  script          Read perf.data (created by perf record) and display trace output
  stat            Run a command and gather performance counter statistics
  test            Runs sanity tests.
  timechart       Tool to visualize total system behavior during a workload
  top             System profiling tool.

 See 'perf help COMMAND' for more information on a specific command.

某些需要特定内核支持的命令可能无法使用。如果想获得每个子命令的具体选项列表,只需输入命令名紧随其后 - h

perf stat -h

 usage: perf stat [<options>] [<command>]

    -e, --event <event>   event selector. use 'perf list' to list available events
    -i, --no-inherit      child tasks do not inherit counters
    -p, --pid <n>         stat events on existing process id
    -t, --tid <n>         stat events on existing thread id
    -a, --all-cpus        system-wide collection from all CPUs
    -c, --scale           scale/normalize counters
    -v, --verbose         be more verbose (show counter open errors, etc)
    -r, --repeat <n>      repeat command and print average + stddev (max: 100)
    -n, --null            null run - dont start any counters
    -B, --big-num         print large numbers with thousands' separators

事件

Perf工具支持一系列的可测量事件。这个工具和底层内核接口可以测量来自不同来源的事件。 例如,一些事件是纯粹的内核计数,在这种情况下的事件被称为软事件 ,例如:context-switches、minor-faults。

另一个事件来源是处理器本身和它的性能监视单元(PMU)。它提供了一个事件列表来测量微体系结构的事件,如周期数、失效的指令、一级缓存未命中等等。 这些事件被称为PMU硬件事件或简称为硬件事件。 它们因处理器类型和型号而异。

perf_events接口还提供了一组通用的的硬件事件名称。在每个处理器,这些事件被映射到一个CPU的真实事件,如果真实事件不存在则事件不能使用。可能会让人混淆,这些事件也被称为硬件事件硬件缓存事件

最后,还有由内核ftrace基础实现的tracepoint事件。但只有2.6.3x和更新版本的内核才提供这些功能。

可以通过命令获得可支持的事件列表:

perf list

List of pre-defined events (to be used in -e):

 cpu-cycles OR cycles                       [Hardware event]
 instructions                               [Hardware event]
 cache-references                           [Hardware event]
 cache-misses                               [Hardware event]
 branch-instructions OR branches            [Hardware event]
 branch-misses                              [Hardware event]
 bus-cycles                                 [Hardware event]

 cpu-clock                                  [Software event]
 task-clock                                 [Software event]
 page-faults OR faults                      [Software event]
 minor-faults                               [Software event]
 major-faults                               [Software event]
 context-switches OR cs                     [Software event]
 cpu-migrations OR migrations               [Software event]
 alignment-faults                           [Software event]
 emulation-faults                           [Software event]

 L1-dcache-loads                            [Hardware cache event]
 L1-dcache-load-misses                      [Hardware cache event]
 L1-dcache-stores                           [Hardware cache event]
 L1-dcache-store-misses                     [Hardware cache event]
 L1-dcache-prefetches                       [Hardware cache event]
 L1-dcache-prefetch-misses                  [Hardware cache event]
 L1-icache-loads                            [Hardware cache event]
 L1-icache-load-misses                      [Hardware cache event]
 L1-icache-prefetches                       [Hardware cache event]
 L1-icache-prefetch-misses                  [Hardware cache event]
 LLC-loads                                  [Hardware cache event]
 LLC-load-misses                            [Hardware cache event]
 LLC-stores                                 [Hardware cache event]
 LLC-store-misses                           [Hardware cache event]

 LLC-prefetch-misses                        [Hardware cache event]
 dTLB-loads                                 [Hardware cache event]
 dTLB-load-misses                           [Hardware cache event]
 dTLB-stores                                [Hardware cache event]
 dTLB-store-misses                          [Hardware cache event]
 dTLB-prefetches                            [Hardware cache event]
 dTLB-prefetch-misses                       [Hardware cache event]
 iTLB-loads                                 [Hardware cache event]
 iTLB-load-misses                           [Hardware cache event]
 branch-loads                               [Hardware cache event]
 branch-load-misses                         [Hardware cache event]

 rNNN (see 'perf list --help' on how to encode it) [Raw hardware event descriptor]

 mem:<addr>[:access]                        [Hardware breakpoint]

 kvmmmu:kvm_mmu_pagetable_walk              [Tracepoint event]

 [...]

 sched:sched_stat_runtime                   [Tracepoint event]
 sched:sched_pi_setprio                     [Tracepoint event]
 syscalls:sys_enter_socket                  [Tracepoint event]
 syscalls:sys_exit_socket                   [Tracepoint event]

 [...]

一个事件可以有子事件(或掩码)。 在某些处理器上的某些事件,可以组合掩码,并在其中一个子事件发生时进行测量。最后,一个事件还可以有修饰符,也就是说,通过过滤器可以改变事件被计数的时间或方式。

硬件事件

PMU硬件事件取决与特定的CPU,由CPU供应商提供文档。如果将Perf工具与libpfm4库链接,则可以提供事件的一些简短描述。有关Intel和AMD处理器的PMU硬件事件的列表,请参阅

使用perf stat进行统计

对于任何支持的事件,Perf可以在进程运行期间持续计数。 在统计模式下,在应用程序运行结束时事件的发生会被简单地汇总并显示在标准输出上。去产生这些统计数据,使用 stat 命令的Perf。 例如:

perf stat -B dd if=/dev/zero of=/dev/null count=1000000

1000000+0 records in
1000000+0 records out
512000000 bytes (512 MB) copied, 0.956217 s, 535 MB/s

 Performance counter stats for 'dd if=/dev/zero of=/dev/null count=1000000':

            5,099 cache-misses             #      0.005 M/sec (scaled from 66.58%)
          235,384 cache-references         #      0.246 M/sec (scaled from 66.56%)
        9,281,660 branch-misses            #      3.858 %     (scaled from 33.50%)
      240,609,766 branches                 #    251.559 M/sec (scaled from 33.66%)
    1,403,561,257 instructions             #      0.679 IPC   (scaled from 50.23%)
    2,066,201,729 cycles                   #   2160.227 M/sec (scaled from 66.67%)
              217 page-faults              #      0.000 M/sec
                3 CPU-migrations           #      0.000 M/sec
               83 context-switches         #      0.000 M/sec
       956.474238 task-clock-msecs         #      0.999 CPUs

       0.957617512  seconds time elapsed

如果没有指定事件,perf stat会收集上面列出的常见事件。一些是软事件例如context-switches,另一些是通用硬件事件例如cycles。在哈希符号之后,可以显示衍生指标,例如“ IPC”(每个周期的指令)。。

事件控制选项

Perf工具可以测量的一个或多个事件。事件使用其符号名称指定,后可选跟随掩码和修饰符。事件名称、掩码和修饰符不区分大小写。

默认情况下,事件同时测量用户和内核级别:

perf stat -e cycles dd if=/dev/zero of=/dev/null count=100000

如果测量仅在用户级别,有增加一个修饰词:

perf stat -e cycles:u dd if=/dev/zero of=/dev/null count=100000

同时测量用户和内核(显式):

perf stat -e cycles:uk dd if=/dev/zero of=/dev/null count=100000

修饰符

事件可以通过冒号添加一个或多个修饰符。 修饰符允许用户对事件计数进行限制。

测量PMU事件,通过下示修饰符:

perf stat -e instructions:u dd if=/dev/zero of=/dev/null count=100000

在这个例子中,我们测量用户级别的指令数量。 注意,对于真实事件,修饰符取决于底层的PMU模型。 修饰符可以随意组合。 这张简单的表格,总结了用于Intel和AMD x86处理器的最常见的修饰符。

修饰符 描述 例子
u priv 3,2,1级别监控(用户) event:u
k priv 0级别监控(内核) event:k
h 在虚拟化环境中监视监控程序事件 event:h
H 在虚拟化环境中监视主机 event:H
G 在虚拟化环境中监视访客机 event:G

以上所有修饰符均视为布尔值(标志)。

硬件事件

要测量硬件供应商文档提供的实际PMU,请传递十六进制参数代码:

perf stat -e r1a8 -a sleep 1

Performance counter stats for 'sleep 1':

            210,140 raw 0x1a8
       1.001213705  seconds time elapsed

多个事件

要测量多个事件,只需提供一个用逗号分隔的列表,其中没有空格:

perf stat -e cycles,instructions,cache-misses [...]

理论上,对事件的数量是没有限制的。如果事件多余实际硬件计数器时,内核会自动多路复用。软事件的数量没有限制。你可以同时测量来自不同来源的事件。

然而,如果每个事件使用一个文件描述符,在per-thread(per-thread模式)或per-cpu(系统范围)模式下,则可能达到内核限制的每个进程的最大文件描述符数。在这种情况下,perf将报告一个错误。有关此问题的帮助,请参阅故障排除部分。

事件的多路复用和缩放

如果事件多于计数器,则内核会使用时间多路复用(开关频率= HZ,通常为100或1000)为每个事件提供访问监视硬件的机会。复用仅适用于PMU事件。使用多路复用时,不会一直测量事件。运行结束时,该工具会根据启用的总时间与运行时间来缩放计数。实际公式为:

final_count = raw_count * time_enabled / time_running 

如果在整个运行过程中都对事件进行了测量,则可以估算该计数是多少。理解这是一个估计值而不是实际计数非常重要。工作负载较重时会有测量丢失,这种情况会在缩放时引入错误。

目前事件以循环方式进行管理,因此每个事件最终都将有机会运行。如果有N个计数器,则循环列表中最多前N个事件被编程到PMU中。在某些情况下它可能小于该值,因为某些事件可能无法一起测量或与它们使用同一计数器。此外,perf_events接口允许多个工具同时测量同一线程或CPU。每个事件都添加到相同的循环队列中。不能保证工具的所有事件都顺序存储在队列中。

为了避免缩放(在只有一个活动perf_event用户),你可以试着减少事件的数量。下表为一些常见的处理器提供计数器的数量:

处理器 通用的计数器 固定的计数器
英特尔酷睿 2 3
英特尔Nehalem 4 3

通用计数器可以测量任何事件,固定计数器只能测量一个事件。一些计数器可能是用于特殊用途,如看门狗定时器。

下面的例子显示缩放的影响:

perf stat -B -e cycles,cycles ./noploop 1

 Performance counter stats for './noploop 1':

    2,812,305,464 cycles
    2,812,304,340 cycles

       1.302481065  seconds time elapsed

在这里,没有多路复用,因此没有缩放。 让我们再添加一个事件:

perf stat -B -e cycles,cycles,cycles ./noploop 1

 Performance counter stats for './noploop 1':

    2,809,725,593 cycles                    (scaled from 74.98%)
    2,810,797,044 cycles                    (scaled from 74.97%)
    2,809,315,647 cycles                    (scaled from 75.09%)

       1.295007067  seconds time elapsed

有多路复用,从而进行缩放。尝试保始终将事件A和B一起测量的方式非常有趣。尽管perf_events内核接口提供了对事件分组的支持,但当前的Perf工具没有。

重复测量

可以使用perf stat多次运行相同的测试工作,并针对每个计数获取均值的标准偏差。

perf stat -r 5 sleep 1

 Performance counter stats for 'sleep 1' (5 runs):

    <not counted> cache-misses
           20,676 cache-references         #     13.046 M/sec   ( +-   0.658% )
            6,229 branch-misses            #      0.000 %       ( +-  40.825% )
    <not counted> branches
    <not counted> instructions
    <not counted> cycles
              144 page-faults              #      0.091 M/sec   ( +-   0.139% )
                0 CPU-migrations           #      0.000 M/sec   ( +-    -nan% )
                1 context-switches         #      0.001 M/sec   ( +-   0.000% )
         1.584872 task-clock-msecs         #      0.002 CPUs    ( +-  12.480% )

       1.002251432  seconds time elapsed   ( +-   0.025% )

在这里,sleep运行5次,并打印每个事件的平均计数以及std-dev/mean的比率。

环境控制选项

Perf工具可用于统计每个线程、每个进程、每个cpu或整个系统的事件。在per-thread模式下,计数器只监视指定线程的执行。当线程被调度出时,监视将停止。当线程从一个处理器迁移到另一个处理器时,计数器在当前处理器上保存,并在新处理器上还原。

per-process模式是per-thread的一个变体,进程中的所有 线程都被监控。计数和采样在进程级别被合计。 perf_events接口允许自动继承fork () pthread_create () 。 默认情况下,Perf工具使能继承。

per-cpu的模式下,指定处理器上所有线程都被监控。计数和采样在每个CPU上合计。一个事件一次只能监视一个CPU。如果元跨多个处理器进行监控,则需要创建多个事件。Perf工具可以统计跨多个处理器计数和采样。它也可以只监视一个部分处理器。

计数和继承

默认情况下, perf stat统计进程的所有线程和后续的子进程和线程。这可以使用 -i选项进行切换。但它无法获得per-thread和per-process的详细计数。

Processor-wide模式

默认情况下, perf stat使用per-thread计数模式。 要按per-cpu计算,请使用-a选项。当选项被设置时,所有在线处理器都会被监视,并且计数会被合计。例如:

perf stat -B -ecycles:u,instructions:u -a dd if=/dev/zero of=/dev/null count=2000000

2000000+0 records in
2000000+0 records out
1024000000 bytes (1.0 GB) copied, 1.91559 s, 535 MB/s

 Performance counter stats for 'dd if=/dev/zero of=/dev/null count=2000000':

    1,993,541,603 cycles
      764,086,803 instructions             #      0.383 IPC

       1.916930613  seconds time elapsed

测量收集了所有CPU上的事件周期和指令。测量的持续时间由dd的执行决定。换句话说,此测量捕获dd进程的执行以及所有cpu上在用户级别以外运行的任何内容。

若要计时测量的持续时间而不消耗周期,可以使用/usr/bin/sleep命令:

perf stat -B -ecycles:u,instructions:u -a sleep 5

 Performance counter stats for 'sleep 5':

      766,271,289 cycles
      596,796,091 instructions             #      0.779 IPC

       5.001191353  seconds time elapsed

可以使用-C选项限制监视cpu的一个子集。可以传递要监视的CPU列表。例如,要在CPU0、CPU2和CPU3上测量:

perf stat -B -e cycles:u,instructions:u -a -C 0,2-3 sleep 5

演示机只有两个CPU,但我们可以限制为CPU 1。

perf stat -B -e cycles:u,instructions:u -a -C 1 sleep 5

 Performance counter stats for 'sleep 5':

      301,141,166 cycles
      225,595,284 instructions             #      0.749 IPC

       5.002125198  seconds time elapsed

计数在所有被监视的CPU上合计。注意,当测量单个CPU时,计数的周期和指令的数量是减半的。

连接到一个正在运行的进程

可以使用Perf连接到已经运行的线程或进程。 这需要具有附加线程或进程ID的权限。若要附加到进程,-p选项必须是进程ID。若要附加到通常在许多Linux计算机上运行的sshd服务,使用:

ps ax | fgrep sshd

 2262 ?        Ss     0:00 /usr/sbin/sshd -D
 2787 pts/0    S+     0:00 fgrep --color=auto sshd

perf stat -e cycles -p 2262 sleep 2

 Performance counter stats for process id '2262':

    <not counted> cycles

       2.001263149  seconds time elapsed

决定测量持续时间的是要执行的命令。即使我们附加到进程,我们仍然可以传递命令的名称。它用于计算测量时间。没有它,Perf将一直监视,直到它被杀死。还要注意,附加到进程时,将监视该进程的所有线程。此外,假设继承在默认情况下处于打开状态,子进程或线程也将被监视。要关闭此功能,必须使用-i选项。可以在进程中附加特定线程。所谓线程,我们指的是内核可见线程。换句话说,通过ps或top命令可见的线程。要附加到线程,必须使用-t选项。我们看一下rsyslogd,因为它总是在Ubuntu 11.04上运行,有多个线程。

ps -L ax | fgrep rsyslogd | head -5

 889   889 ?        Sl     0:00 rsyslogd -c4
 889   932 ?        Sl     0:00 rsyslogd -c4
 889   933 ?        Sl     0:00 rsyslogd -c4
 2796  2796 pts/0    S+     0:00 fgrep --color=auto rsyslogd

perf stat -e cycles -t 932 sleep 2

 Performance counter stats for thread id '932':

    <not counted> cycles

       2.001037289  seconds time elapsed

在本例中,线程932在测量的2s期间没有运行。否则,我们将看到一个计数值。附加到内核线程是可能的,但实际上并不推荐这样做。考虑到内核线程倾向于固定到特定的CPU,最好使用cpu-wide模式。

控制输出选项

perf stat可以修改输出以满足不同的需求。

大数字输出

对大多数人来说,很难读懂很大的数字。使用perf stat,可以使用逗号分隔符打印数千个大数字(美式)。为此,必须设置-B选项和设置正确的语言环境LC_NUMERIC。如上面的例子所示,Ubuntu已经正确地设置了语言环境信息。显式调用如下所示:

LC_NUMERIC=en_US.UTF8 perf stat -B -e cycles:u,instructions:u dd if=/dev/zero of=/dev/null count=10000000

100000+0 records in
100000+0 records out
51200000 bytes (51 MB) copied, 0.0971547 s, 527 MB/s

 Performance counter stats for 'dd if=/dev/zero of=/dev/null count=100000':

       96,551,461 cycles
       38,176,009 instructions             #      0.395 IPC

       0.098556460  seconds time elapsed

机器可读的输出

perf stat还可以打印计数,格式可以很容易地导入到电子表格或由脚本进行解析。-x选项改变输出的格式,并允许用户传递分隔符。这使得很容易生成CSV样式的输出:

perf stat  -x, date

Thu May 26 21:11:07 EDT 2011
884,cache-misses
32559,cache-references
<not counted>,branch-misses
<not counted>,branches
<not counted>,instructions
<not counted>,cycles
188,page-faults
2,CPU-migrations
0,context-switches
2.350642,task-clock-msecs

请注意,选项-x -B 不兼容。

使用Perf记录采样

perf工具可用于收集per-thread、per-process和per-cpu的性能数据。

有几个与采样相关的命令:record、report、annotate。必须首先使用perf record收集样本。这将生成一个名为perf.data的输出文件。然后,可以使用perf reportperf annotate命令分析该文件(可能在另一台计算机上)。该方式类似于OProfile。

基于事件的采样

Perf_events基于基于事件的采样。周期表示为事件发生的次数,而不是计时器计时的次数。当采样计数器溢出时,即从2^64换回0时,记录采样。PMU没有实现64位硬件计数器,但perf_events在软件中模拟该计数器。

perf_events模拟64位计数器的方式仅限于使用实际硬件计数器中的位数来表示采样周期。在小于64位的情况下,内核会自动截断周期。因此,如果在32位系统上运行,最好周期始终小于2^31。

在计数器溢出时,内核记录有关程序执行的信息,也就是采样。记录的内容取决于测量的类型。这都是由使用者和工具指定的。但一般来说,样本中的关键信息是指令指针,即时程序中断在哪里。

基于中断的采样在现代处理器上引入了skid。这意味着每个采样的指令指针指向程序处理PMU中断的位置,而不是计数器实际溢出的位置,即它在采样周期结束时的位置。在某些情况下,如果有分支,这两个点之间的距离可能是几十条或更多的指令。当程序不再向前运行时,这两个位置确实是相同的。因此,在解释分析数据时必须小心。

默认事件:时钟周期计数

默认情况下, perf record使用时钟周期事件做为抽样事件。这是由内核映射到特定PMU事件的一个通用的硬件事件。对于英特尔来说,映射到 UNHALTED_CORE_CYCLES 。在CPU频率扩展的情况下,此事件在时间上不能保持恒定不变。英特尔提供了另一个名为UNHALTED_REFERENCE_CYCLES 的事件,但此事件当前不适用于perf_events。

在AMD系统中,事件映射到 CPU_CLK_UNHALTED事件,这个事件也受到频率扩展的影响。 在任何英特尔或AMD处理器,周期事件在处理器空闲时不计数,例如当它调用 mwait ()

时间和速度

perf_events接口允许两种模式表达采样周期:

  • 事件发生的次数(时间)
  • 样本/秒的平均速率(频率)

Perf工具默认使用平均速率。它设置为1000 hz,或1000样本/秒。 这意味着内核会动态调整采样周期以达到目标平均速率。周期内的调整会在原始的分析数据中报告。与此相反,与其他模式相比,采样周期由用户设置,并且在采样之间不发生变化。目前不支持随机采样周期。

收集样本

默认情况下,perf record在运行在per-thread模式下,并且开始继承模式。当执行一个繁忙循环的简单程序时,简单的使用如下:

perf record ./noploop 1

[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.002 MB perf.data (~89 samples) ]

上面的示例以1000Hz的平均目标速率收集事件周期的样本。生成的示例将保存到perf.data文件中。如果文件已经存在,可能会提示您通过-F覆盖它。要将结果放入特定文件中,请使用-o选项。

警告:报告的样本数只是估计值。它没有反映实际采集的样本数量。此估计基于写入perf.data文件的字节数和最小样本大小。但每个样本的真正大小取决于测量的类型。一些样本由计数器本身生成,而另一些样本则与后处理期间支持符号相关,例如mmap()信息。

要获取perf.data文件的准确样本数,可以使用perf report命令:

perf record ./noploop 1

[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.058 MB perf.data (~2526 samples) ]
perf report -D -i perf.data | fgrep RECORD_SAMPLE | wc -l

1280

指可使用 -F选项自定义采样速度。 例如,仅在用户级别对事件指令进行采样,并且使用250个样本/秒的平均速率:

at an average rate of 250 samples/sec:
perf record -e instructions:u -F 250 ./noploop 4

[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.049 MB perf.data (~2160 samples) ]

要指定采样周期,可以使用-c选项。例如,仅在用户级别收集每2000次事件指令的采样,请执行以下操作:

perf record -e retired_instructions:u -c 2000 ./noploop 4

[ perf record: Woken up 55 times to write data ]
[ perf record: Captured and wrote 13.514 MB perf.data (~590431 samples) ]

Processor-wide模式

在per-cpu模式下,收集受监控cpu上执行的所有线程的样本。要在per-cpu模式下切换perf record,需要使用-a选项。默认情况下,在此模式下,所有联机CPU都被监视。正如上面perf stat所解释的,可以使用-C选项限制到CPU的一个子集。

要在所有CPU上以1000个样本/秒的平均目标速率对用户和内核级别的周期采样5秒:

perf record -a -F 1000 sleep 5

[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.523 MB perf.data (~22870 samples) ]

使用perf report分析采样

perf record收集的样本会保存到一个二进制文件中,默认情况下,该文件名为perf.dataperf report命令读取此文件并生成简明的执行概要文件。默认情况下,样本按函数排序,样本数最多的优先。可以自定义排序顺序,从而以不同的方式查看数据。

perf report

# Events: 1K cycles
#
# Overhead          Command                   Shared Object  Symbol
# ........  ...............  ..............................  .....................................
#
    28.15%      firefox-bin  libxul.so                       [.] 0xd10b45
     4.45%          swapper  [kernel.kallsyms]               [k] mwait_idle_with_hints
     4.26%          swapper  [kernel.kallsyms]               [k] read_hpet
     2.13%      firefox-bin  firefox-bin                     [.] 0x1e3d
     1.40%  unity-panel-ser  libglib-2.0.so.0.2800.6         [.] 0x886f1
     [...]

“Overhead”列指示在相应函数中收集的总样本的百分比。第二列显示被收集样本的进程。在per-thread/per-process模式下,这始终是受监视命令的名称。但在cpu-wide模式下,命令可能会有所不同。第三列显示了样本来源的ELF镜像的名称。如果程序是动态链接的,则这可能会显示共享库的名称。当样本来自内核时,使用伪ELF镜像名[kernel.kallsyms]。第四列指示样本运行的级别,也就是程序被中断时正在运行的级别:

最后一列显示了符号名称。

样本可以使用多种方式进行呈现,即使用排序。例如按共享对象进行排序,使用dso:

perf report --sort=dso

# Events: 1K cycles
#
# Overhead                   Shared Object
# ........  ..............................
#
    38.08%  [kernel.kallsyms]
    28.23%  libxul.so
     3.97%  libglib-2.0.so.0.2800.6
     3.72%  libc-2.13.so
     3.46%  libpthread-2.13.so
     2.13%  firefox-bin
     1.51%  libdrm_intel.so.1.0.0
     1.38%  dbus-daemon
     1.36%  [drm]
     [...]

输出控制选项

为使输出更容易解析,可以修改列分隔符为某一个字符:

perf report -t

内核报告控制选项

Perf工具不知道如何从压缩内核映像(vmlinuz)中提取符号。因此,用户必须将非压缩内核镜像的路径通过 -k 传递给Perf:

perf report -k /tmp/vmlinux

当然,内核镜像只有带debug符号编译的才能工作。

Processor-wide模式

在per-cpu的模式中,会从监控CPU上的所有线程上记录的样本。 这样,我们可以收集来自许多不同进程的样本。例如,如果我们监视所有CPU 5s:

perf record -a sleep 5
perf report

# Events: 354  cycles
#
# Overhead          Command               Shared Object  Symbol
# ........  ...............  ..........................  ......................................
#
    13.20%          swapper  [kernel.kallsyms]           [k] read_hpet
     7.53%          swapper  [kernel.kallsyms]           [k] mwait_idle_with_hints
     4.40%    perf_2.6.38-8  [kernel.kallsyms]           [k] _raw_spin_unlock_irqrestore
     4.07%    perf_2.6.38-8  perf_2.6.38-8               [.] 0x34e1b
     3.88%    perf_2.6.38-8  [kernel.kallsyms]           [k] format_decode
     [...]

当符号打印为十六进制地址时,这是因为ELF镜像没有符号表。当二进制文件被剥离时就会发生这种情况。我们也可以按cpu排序。这可能有助于确定工作负载是否平衡:

perf report --sort=cpu

# Events: 354  cycles
#
# Overhead  CPU
# ........  ...
#
   65.85%  1
   34.15%  0

计算开销

perf收集调用链时,开销可以在两列中显示为“Children”和“Self”。“self”开销只是通过所有入口(通常是一个函数,也就是符号)的所有周期值相加来计算的。这是perf传统显示方式,所有“self”开销值之和应为100%。

“children”开销是通过将子函数的所有周期值相加来计算的,这样它就可以显示更高级别函数的总开销,即使它们不直接参与更多的执行。这里的“Children”表示从另一个(父)函数调用的函数。

所有“children”开销值之和超过100%可能会令人困惑,因为它们中的每一个已经是其子函数的“self”开销的累积。但是如果启用了这个功能,用户可以找到哪一个函数的开销最大,即使样本分布在子函数上。

考虑下面的例子,有三个函数如下所示。

void foo(void) {
    /* do something */
}

void bar(void) {
    /* do something */
    foo();
}

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

在本例中,“foo”是“bar”的子级,“bar”是“main”的直接子级,因此“foo”也是“main”的子级。换句话说,“main”是“foo”和“bar”的父级,“bar”是“foo”的父级。

假设所有样本都只记录在“foo”和“bar”中。当使用调用链记录时,输出将在perf report的常规(仅自开销)输出中显示如下内容:

Overhead  Symbol
........  .....................
  60.00%  foo
          |
          --- foo
              bar
              main
              __libc_start_main

  40.00%  bar
          |
          --- bar
              main
              __libc_start_main

启用--children选项时,子函数(即'foo'和'bar')的'self'开销值将添加到父函数中,以计算'children'开销。在这种情况下,报告可以显示为:

Children      Self  Symbol
........  ........  ....................
 100.00%     0.00%  __libc_start_main
          |
          --- __libc_start_main

 100.00%     0.00%  main
          |
          --- main
              __libc_start_main

 100.00%    40.00%  bar
          |
          --- bar
              main
              __libc_start_main

  60.00%    60.00%  foo
          |
          --- foo
              bar
              main
              __libc_start_main

在上述输出中,“foo”的“self”开销(60%)被添加到“bar”、“main”和“__libc_start_main”的“children”开销中。同样,“bar”的“self”开销(40%)添加到“main”和“libc”的“children”开销中。

因此,首先显示'__libc_start_main'和'main',因为它们有相同(100%)的“子”开销(即使它们没有“自”开销),并且它们是'foo'和'bar'的父级。

从v3.16开始,默认情况下会显示“children”开销,并按其值对输出进行排序。通过在命令行上指定--no-children选项或在perf配置文件中添加“report.children=false”或“top.children=false”,禁用“children”开销。

使用perf annotate分析源码

可以使用perf annotate深入到指令级分析。为此,需要使用要解析的命令的名称调用perf annotate。所有带样本的函数都将被反汇编,每条指令都将报告其样本的相对百分比:

perf record ./noploop 5
perf annotate -d ./noploop

------------------------------------------------
 Percent |   Source code & Disassembly of noploop.noggdb
------------------------------------------------
         :
         :
         :
         :   Disassembly of section .text:
         :
         :   08048484 <main>:
    0.00 :    8048484:       55                      push   %ebp
    0.00 :    8048485:       89 e5                   mov    %esp,%ebp
[...]
    0.00 :    8048530:       eb 0b                   jmp    804853d <main+0xb9>
   15.08 :    8048532:       8b 44 24 2c             mov    0x2c(%esp),%eax
    0.00 :    8048536:       83 c0 01                add    $0x1,%eax
   14.52 :    8048539:       89 44 24 2c             mov    %eax,0x2c(%esp)
   14.27 :    804853d:       8b 44 24 2c             mov    0x2c(%esp),%eax
   56.13 :    8048541:       3d ff e0 f5 05          cmp    $0x5f5e0ff,%eax
    0.00 :    8048546:       76 ea                   jbe    8048532 <main+0xae>
[...]

第一列报告在该指令在捕获函数==noploop()==的样本百分比。如前所述,您应该仔细解读这些信息。

如果使用-ggdb编译应用程序,perf annotate可以生成源代码级信息。下面的代码片段显示了在使用此调试信息编译noploop时,同一次执行noploop时的更多信息输出。

------------------------------------------------
 Percent |   Source code & Disassembly of noploop
------------------------------------------------
         :
         :
         :
         :   Disassembly of section .text:
         :
         :   08048484 <main>:
         :   #include <string.h>
         :   #include <unistd.h>
         :   #include <sys/time.h>
         :
         :   int main(int argc, char **argv)
         :   {
    0.00 :    8048484:       55                      push   %ebp
    0.00 :    8048485:       89 e5                   mov    %esp,%ebp
[...]
    0.00 :    8048530:       eb 0b                   jmp    804853d <main+0xb9>
         :                           count++;
   14.22 :    8048532:       8b 44 24 2c             mov    0x2c(%esp),%eax
    0.00 :    8048536:       83 c0 01                add    $0x1,%eax
   14.78 :    8048539:       89 44 24 2c             mov    %eax,0x2c(%esp)
         :           memcpy(&tv_end, &tv_now, sizeof(tv_now));
         :           tv_end.tv_sec += strtol(argv[1], NULL, 10);
         :           while (tv_now.tv_sec < tv_end.tv_sec ||
         :                  tv_now.tv_usec < tv_end.tv_usec) {
         :                   count = 0;
         :                   while (count < 100000000UL)
   14.78 :    804853d:       8b 44 24 2c             mov    0x2c(%esp),%eax
   56.23 :    8048541:       3d ff e0 f5 05          cmp    $0x5f5e0ff,%eax
    0.00 :    8048546:       76 ea                   jbe    8048532 <main+0xae>
[...]

使用perf annotate分析内核

Perf工具不知道如何从压缩内核镜像(vmlinuz)中提取符号。正如perf report中的示例,用户必须通过-k 传递非压缩内核镜像的路径:

perf annotate -k /tmp/vmlinux -d symbol

在一次说明,这只使用带debug符号编译的内核。

使用perf top进行现场分析

perf工具可以以类似于Linux top工具的模式运行,实时打印采样函数。默认的采样事件是cycles,默认的顺序是每个符号的采样数递减,因此perf top显示了花费大部分时间的函数。默认情况下,perf top以processor-wide模式运行,在用户和内核级别监视所有在线的CPU。使用-C选项可以只监视CPU的一个子集。

perf top
-------------------------------------------------------------------------------------------------------------------------------------------------------
  PerfTop:     260 irqs/sec  kernel:61.5%  exact:  0.0% [1000Hz
cycles],  (all, 2 CPUs)
-------------------------------------------------------------------------------------------------------------------------------------------------------

            samples  pcnt function                       DSO
            _______ _____ ______________________________ ___________________________________________________________

              80.00 23.7% read_hpet                      [kernel.kallsyms]
              14.00  4.2% system_call                    [kernel.kallsyms]
              14.00  4.2% __ticket_spin_lock             [kernel.kallsyms]
              14.00  4.2% __ticket_spin_unlock           [kernel.kallsyms]
               8.00  2.4% hpet_legacy_next_event         [kernel.kallsyms]
               7.00  2.1% i8042_interrupt                [kernel.kallsyms]
               7.00  2.1% strcmp                         [kernel.kallsyms]
               6.00  1.8% _raw_spin_unlock_irqrestore    [kernel.kallsyms]
               6.00  1.8% pthread_mutex_lock             /lib/i386-linux-gnu/libpthread-2.13.so
               6.00  1.8% fget_light                     [kernel.kallsyms]
               6.00  1.8% __pthread_mutex_unlock_usercnt /lib/i386-linux-gnu/libpthread-2.13.so
               5.00  1.5% native_sched_clock             [kernel.kallsyms]
               5.00  1.5% drm_addbufs_sg                 /lib/modules/2.6.38-8-generic/kernel/drivers/gpu/drm/drm.ko

默认情况下,第一列显示自运行开始以来的总样本数。通过按“Z”键,可以将其更改为打印自上次刷新以来的样本数。当处理器不处于暂停状态(即不空闲)时,cycle事件也会统计CPU周期。因此,这不等于墙面时间。此外,事件还受频率扩展的影响。

也可以深入到单个函数中,查看哪些指令具有最多的样本。要深入到指定函数,请按“s”键并输入函数名。这里我们选择了顶部函数noploop(上面没有显示):

------------------------------------------------------------------------------------------------------------------------------------------
   PerfTop:    2090 irqs/sec  kernel:50.4%  exact:  0.0% [1000Hz cycles],  (all, 16 CPUs)
------------------------------------------------------------------------------------------------------------------------------------------
Showing cycles for noploop
  Events  Pcnt (>=5%)
       0  0.0%   00000000004003a1 <noploop>:
       0  0.0%     4003a1:   55                      push   %rbp
       0  0.0%     4003a2:   48 89 e5                mov    %rsp,%rbp
    3550 100.0%    4003a5:   eb fe                   jmp    4003a5 <noploop+0x4>

使用perf bench进行基准测试

perf bench命令包含多个多线程微内核基准测试,用于在Linux内核和系统调用中执行不同的子系统。这使得黑客可以轻松地测量更改的影响,从而帮助缓解性能衰退。

它还充当一个通用的基准框架,使开发人员能够轻松地创建测试用例、透明进行整合和使用富性能工具子系统。

sched:调度器基准测试

测量多个任务之间的pipe(2)和socketpair(2)操作。允许测量线程与进程上下文切换的性能。

$perf bench sched messaging -g 64
# Running 'sched/messaging' benchmark:
# 20 sender and receiver processes per group
# 64 groups == 2560 processes run

     Total time: 1.549 [sec]

mem:内存访问基准测试

numa: numa调度和MM基准测试

futex: futex压力基准测试

处理futex内核实现的细粒度方面。它对于内核黑客非常有用。它目前支持唤醒和重新排队/等待操作,并强调私有和共享futexes的哈希方案。下面时nCPU线程运行的一个示例,每个线程处理1024个futex来测量哈希逻辑:

$ perf bench futex hash
# Running 'futex/hash' benchmark:
Run summary [PID 17428]: 4 threads, each operating on 1024 [private] futexes for 10 secs.

[thread  0] futexes: 0x2775700 ... 0x27766fc [ 3343462 ops/sec ]
[thread  1] futexes: 0x2776920 ... 0x277791c [ 3679539 ops/sec ]
[thread  2] futexes: 0x2777ab0 ... 0x2778aac [ 3589836 ops/sec ]
[thread  3] futexes: 0x2778c40 ... 0x2779c3c [ 3563827 ops/sec ]

Averaged 3544166 operations/sec (+- 2.01%), total secs = 10

故障诊断和建议

本节列出了很多建议来避免使用Perf时常见的陷阱。

打开文件的限制

Perf工具所使用的perf_event内核接口的设计是这样的:它为per-thread或per-cpu的每个事件使用一个文件描述符。

在16-way系统上,当您这样做时:

perf stat -e cycles sleep 1

您实际上创建了16个事件,从而消耗了16个文件描述符。

在per-thread模式下,当您在同一16-way系统上对具有100个线程的进程进行采样时:

perf record -e cycles my_hundred_thread_process

然后,一旦创建了所有的线程,您将得到100*1(event)*16(cpus)=1600个文件描述符。Perf在每个CPU上创建一个事件实例。只有当线程在该CPU上执行时,事件才能有效地度量。这种方法加强了采样缓冲区的局部性,从而减少了采样开销。在运行结束时,该工具将所有样本合计到一个输出文件中。

如果Perf因“打开的文件太多”错误而中止,有以下几种解决方案:

  • 使用ulimit-n增加每个进程打开的文件数。注意:您必须是root
  • 限制一次运行中测量的事件数
  • 限制正在测量的CPU数量

增加打开文件限制

超级用户可以更改进程打开的文件限制,使用 ulimit shell内置命令:

ulimit -a
[...]
open files                      (-n) 1024
[...]

ulimit -n 2048
ulimit -a
[...]
open files                      (-n) 2048
[...]

使用build-id表示二进制文件

perf record命令在perf.data中保存者与测量相关的所有ELF镜像的唯一标识符。在per-thread模式下,这包括被监视进程的所有ELF镜像。在cpu-wide模式下,它包括系统上运行的所有进程。如果使用-Wl,--build-id选项,则链接器将生成这些唯一标识符。因此,它们被称为build-id。当将指令地址与ELF映像关联时,build id是一个非常有用的工具。要提取perf.data文件中使用的所有生成id项,请发出:

perf buildid-list -i perf.data

06cb68e95cceef1ff4e80a3663ad339d9d6f0e43 [kernel.kallsyms]
e445a2c74bc98ac0c355180a8d770cd35deb7674 /lib/modules/2.6.38-8-generic/kernel/drivers/gpu/drm/i915/i915.ko
83c362c95642c3013196739902b0360d5cbb13c6 /lib/modules/2.6.38-8-generic/kernel/drivers/net/wireless/iwlwifi/iwlcore.ko
1b71b1dd65a7734e7aa960efbde449c430bc4478 /lib/modules/2.6.38-8-generic/kernel/net/mac80211/mac80211.ko
ae4d6ec2977472f40b6871fb641e45efd408fa85 /lib/modules/2.6.38-8-generic/kernel/drivers/gpu/drm/drm.ko
fafad827c43e34b538aea792cc98ecfd8d387e2f /lib/i386-linux-gnu/ld-2.13.so
0776add23cf3b95b4681e4e875ba17d62d30c7ae /lib/i386-linux-gnu/libdbus-1.so.3.5.4
f22f8e683907b95384c5799b40daa455e44e4076 /lib/i386-linux-gnu/libc-2.13.so
[...]

build-id缓存

每次运行结束时,perf record命令都会更新一个build id缓存,其中包含带有样本的ELF镜像的新条目。缓存包含:

  • 带样本的ELF镜像的build-id
  • 带有样本的ELF镜像的副本

给定的build-id是不可变的,它们唯一地标识二进制文件。如果重新编译二进制文件,将生成新的build-id,并在缓存中保存ELF图像的新副本。缓存保存在磁盘上的默认目录$HOME/.debug中。系统管理员可以使用全局配置文件==/etc/perfconfig==为缓存指定备用全局目录:

$ cat /etc/perfconfig
[buildid]
dir = /var/tmp/.debug

在某些情况下,关掉 build-id 缓存更新可能时有益的。为此,你需要使用perf record-n选项 性能记录

perf record -N dd if=/dev/zero of=/dev/null count=100000

访问控制

对于某些事件,必须是root才能调用perf工具。本文档假定用户具有root权限。如果您试图在权限不足的情况下运行perf,它将报告

No permission to collect system-wide stats.

其他场景

分析睡眠时间

此功能显示程序在何处睡眠或等待某物的时间和时间。

第一步是收集数据。我们需要收集sched_stat和sched_switch事件。Sched_stat事件是不够的,因为它们是在任务的上下文中生成的,这会唤醒目标任务(例如释放锁)。我们需要相同的事件,但带有目标任务的调用链。此调用链可以从之前的sched_switch事件中提取。

第二步是合并sched_startsched_switch事件。这可以通过“perf-inject-s”来完成。

$ ./perf record -e sched:sched_stat_sleep -e sched:sched_switch  -e sched:sched_process_exit -g -o ~/perf.data.raw ~/foo
$ ./perf inject -v -s -i ~/perf.data.raw -o ~/perf.data
$ ./perf report --stdio --show-total-period -i ~/perf.data
# Overhead        Period  Command      Shared Object          Symbol
# ........  ............  .......  .................  ..............
#
  100.00%     502408738      foo  [kernel.kallsyms]  [k] __schedule
               |
               --- __schedule
                   schedule
                  |          
                  |--79.85%-- schedule_hrtimeout_range_clock
                  |          schedule_hrtimeout_range
                  |          poll_schedule_timeout
                  |          do_select
                  |          core_sys_select
                  |          sys_select
                  |          system_call_fastpath
                  |          __select
                  |          __libc_start_main
                  |          
                   --20.15%-- do_nanosleep
                             hrtimer_nanosleep
                             sys_nanosleep
                             system_call_fastpath
                             __GI___libc_nanosleep
                             __libc_start_main
$cat foo.c
...
          for (i = 0; i <  10; i++) {
                  ts1.tv_sec = 0;
                  ts1.tv_nsec = 10000000;
                  nanosleep(&ts1, NULL);

                  tv1.tv_sec = 0;
                  tv1.tv_usec = 40000;
                  select(0, NULL, NULL, NULL,&tv1);
          }
...

参考文档:

Linux kernel profiling with perf

阅读 6.6k

老王系统屋
做为一个不称职的老年码农,一直疏忽整理笔记,开博记录一下,用来丰富老年生活,

做为一个不称职的老年码农,一直疏忽整理笔记,开博记录一下,用来丰富老年生活,

58 声望
12 粉丝
0 条评论

做为一个不称职的老年码农,一直疏忽整理笔记,开博记录一下,用来丰富老年生活,

58 声望
12 粉丝
宣传栏