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。 为了可读性,一些输出使用省略号( […] )。php

命令

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

支持的命令列表:git

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 shell

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的真实事件,若是真实事件不存在则事件不能使用。可能会让人混淆,这些事件也被称为硬件事件硬件缓存事件sass

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

能够经过命令得到可支持的事件列表:app

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

相关文章
相关标签/搜索