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

相關文章
相關標籤/搜索