###### tags: `Profiling` # Performance Evaluation: perf, eBPF profiling 有很多種方式,傳統上會使用編譯器在程式中插入 hook,藉此追蹤程式的函式、call graph 等,工具如 gprof, gcc 旗標 finstrument-functions 都是用此方式,他們的缺點是對程式的 overhead 會很高。另一種較為低成本的方式為 sample-based profiling。 perf 和 eBPF 為 Linux 系統中用來做效能分析的重要工具,以下將介紹者兩個工具的架構及使用方法。進行效能分析時常會用到兩種方式 sample-based profiling 及 tracing: - sample-based profiling: 以某個頻率對系統或程式取樣,如果某個函式被取樣比重最高,那很有機會是程式的熱點。 - tracing: 紀錄每次事件的發生 一般來說 sample-based profiling 的成本會比 tracing 低,根據事件發生的頻率,tracing 可能會造成很大的 overhead。 :::info 以下提供一個簡單的 overhead 計算公式,可用在 profiling 和 tracing: > Overhead = (frequency × action performed) / CPUs action performed - profiling: 每一次取樣時產生的成本 - tracing: 每一次紀錄事件產生的成本 ::: ## perf: The Offical Linux Profiler perf support many profiling/tracing features - CPU Performance Monitoring Units (PMU) also called Performance Monitoring Counters (PMCs) (**Hardware events**) - e.g. branch-misses,cache-misses,cpu-cycles,instructions - OS related events (**Software events**) - e.g. context-switches, page-faults - ==Statically defined tracepoints== (**tracepoints**) - ==User and kernel dynamic tracing== (**uprobes** and **kprobes**) - Kernel line and local variable tracing - Efficient in-kernel counts and filters - Stack tracing, libunwind - Code annotation 當紀錄有關硬體的效能事件時,必須與硬體的 PMU (Performance Monitoring Unit) 互動,以 intel 處理器來說,PMU 是由 MSRs (Model-Specific Registers) 所控制,並使用 WRMSR, RDMSR 指令操作,MSR 控制目前要紀錄的事件,以及事件發生的次數。 其他軟體事件會由作業系統實作,如 tracepoints, kprobes 。 所有與效能有關的事件在 Linux 中都稱為 perf_events,詳細的事件列表可使用 `perf list` 查看。要注意使用某些事件可能需要更高的權限。 ### perf 的兩種主要模式 - `perf stat` - `perf record` ![](https://i.imgur.com/oAAAIpe.png) ### perf stat: Counting events for a program - Uses an efficient in-kernel counter, and prints the results perf stat 紀錄硬體事件會使用到 PMU counting mode 統計事件發生的次數。 perf stat 只能執行一個程式並得知與效能相關的統計結果,如統計程式中發生了多少次 cache-misses,但無法得知是程式中的哪個函式觸發此事件,因此會使用 perf record 對程式做更深入的分析。 ### perf record: Profiling program 在這種模式下紀錄到的事件不會是真實發生的次數,因為是透過<mark>**取樣**</mark>,如果是紀錄硬體事件會用到 PMU Sampling mode perf record 有兩種方式可以指定多久取樣一次,對事件設定一個週期 (period) 或設定一個頻率 (frequency) 進行取樣。 - **Period** `-c` or `--count` : 指定==事件發生多少次之後才進行取樣==。當紀錄硬體事件時 Linux 會在 PMU 設定一個數值,當 PMU 裡的計數器溢位時會觸發中斷,並用 instruction pointer 等暫存器紀錄程式狀態, 如指定每發生一百萬次 cache misses 取樣一次可以使用 `perf record -e 'cache-misses' -c 1000000 -- ./profiled_program` - **Freqency** `-F` or `--freq`: 指定==一秒要取樣的次數==,這種模式下 Linux 會根據事件產生的快慢,動態調整取樣週期,盡量達到使用者指定的頻率。 下文引用 perf_event_open(2) > <u>**sample_period**</u>, <u>**sample_freq**</u> > > A "sampling" event is one that ==generates an overflow notification every N events==, where N is given by **sample_period**. A sampling event has sample_period > 0. When an overflow occurs, requested data is recorded in the mmap buffer. The sample_type field controls what data is recorded on each overflow. > > **sample_freq** can be used if you wish to use frequency rather than period. In this case, you set the freq flag. ==The kernel will adjust the sampling period== to try and achieve the desired rate. The rate of adjustment is a timer tick 下圖示範 Linux 根據頻率調整取樣週期,以兩個硬體事件 Branch, instruction 為例,x 軸為 perf 取樣的次數,只列出前面 100 個取樣,y 軸為 Linux 設定的事件取樣週期,可以看到在不同的時間點,Linux 指定的取樣週期都不一樣。 ![](https://i.imgur.com/Mf1jbeJ.png) 對使用者來說用 `-F` 會比較方便而且非常有用,因為不需要知道某個事件一秒鐘會發生幾次,如果取樣到的事件太少,再增加頻率就可以。 以下為各種常見軟硬體事件一秒產生的次數,根據使用者執行的 workload 會有所差距 [^1] [^1]: BPF Performance Tools Ch18 ![](https://i.imgur.com/k8zSpro.png) 要注意的是由於現代處理器的設計都有 pipeline,而且從計數器溢位到觸發中斷也有延遲,因此 perf 紀錄的指令位址不一定會是觸發事件的指令,這種現象通常稱為 skid。同時 speculative execution 會執行到錯誤預測的分支,這也會讓這些指令產生的事件加到 PMU 的計數器中。 :::info perf 也支援更進階 profiling 的操作,如 PEBS (Precise Event-based Sampling) 可以解決以上的問題,PEBS 直接使用硬體的緩衝區,精準紀錄 PMU 事件發生當下的 instruction pointer。 可參考 *Advanced Hardware Profiling and Sampling (PEBS, IBS, etc.): Creating a New PAPI Sampling Interface* ::: 以下用示意圖說明 perf record 取樣的方法 (雖然以下是用取樣 stack,但可以應用在不同的 perf_event) perf record 藉由一個使用者以 `-F` 參數設定一秒中要取樣的次數,當程式從 CPU 置換出去時則不取樣。 ![](https://i.imgur.com/bNhI409.png) ### Stack Sampling (perf record) perf record 也可以對程式的 stack 做取樣, 基本用法,`-g` 指定 stack trace,`--` 後面接著要 profile 的程式: ![](https://i.imgur.com/nFMEcg1.png) <br> ### Hands-on Examples http://www.brendangregg.com/perf.html Branden Gregg 在他的 blog 提供非常多現成的命令,可參考他的文章。 以下提供一些範例 - 每發生 100 次 Last Level Cache miss 時取樣一次 stack (整個系統的程式) ``` # perf record -e LLC-load-misses -c 100 -ag -- sleep 5 ``` ### perf 的實作方式 ![](https://i.imgur.com/QSEH2Og.png) ### Flame Graph 當要了解一個大型程式架構的效能時,可以用 Flame Graph 視覺化效能瓶頸。 每一個 Linux 版本可以用的方式都不太一樣,以 Flame Graph 在 github 上使用手冊的說明是使用 Linux 2.6 的流程 ![](https://i.imgur.com/yT45FRH.png) :::success 編譯器在為某些硬體最佳化時,可能會將 frame pointer register 當作 general purpose register 使用,這會讓 perf 在走訪堆疊時失敗 (Broken Stacks),因此編譯時必須加上 `-fno-omit-frame-pointer` 也可使用 `perf record -g dwarf` ::: ## eBPF BPF 最早在 1992 年由 Steven McCanne 和 Van Jacobson 提出,包含 BPF 指令集和一個 **in-kernel VM**,用來在不同的硬體架構上過濾網路封包。後來社群發現 BPF 的能力可以用來做效能分析,因此對 Linux kernel 和 BPF 做了很大的改善,目前都通稱現在的版本為 eBPF (或直接稱作 BPF),而以前提出的版本則稱作 cBPF。 以下為 cBPF 和 eBPF 的差異,eBPF 不但有更多的暫存器也有更大的 stack,但最重要的是 eBPF 的 Map,eBPF 程式能夠寫入資料到 Map,同時 userspace 也能存取 Map 的資料,因此提供了一個在 kernel space 和 user space 交換資料的機制,同時也把 "**狀態**" 這的概念帶到 eBPF 中,這是 cBPF 所沒有的。 ![](https://i.imgur.com/qPdCn0z.png) ### 使用 eBPF 的專案 as of 2020: ![](https://i.imgur.com/GzaoEHQ.png) BPF 時間表: https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md - 3.15: Optimization of BPF interpreter instruction set - 3.18: Linux eBPF was released (bpf syscall), Tables (a.k.a. Maps) - 3.19: Socket support - 4.1: BPF attached to kprobes (能夠將 eBPF 註冊到 kprobe 上) - 4.7: BPF attached to tracepoints - 4.9: BPF attached to perf events - 4.10: cgroups support - 4.18: bpfilter 以上的時間表可以看到 eBPF 可以註冊 (或掛載,原文為 attach) 到 Linux 不同的地方,又可稱為 hook point,可以用一張圖總結所有 eBPF 能夠註冊到的地方,如 kprobe, uprobe, perf_events (包含下圖的 PMCs 和 Software Envets) 等,每一個可註冊的點都對應一種 eBPF 程式的型態,意思是 eBPF 的 function prototype 和可以使用的 bpf helper function 可能會有所差異,可參考 https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md#program-types ![](https://i.imgur.com/1zlckrW.png) <br> 要執行 eBPF 需要 kernel 的支援,以下解釋幾個重要的元件: - interpreter: in-kernel VM 用來安全的執行 eBPF bytecode,可開啟 JIT 加速 (ubuntu 18.04 預設為開啟) - hook point: eBPF 的掛載點,當事件觸發時會執行掛載到 hook point 的 eBPF 程式 - Map: array, hash 等資料結構,eBPF 程式和 userspace 都可以存取資料 - helper: eBPF 可以呼叫的函式,根據 eBPF 程式型態會有所差異 以下解釋 eBPF 的註冊以及執行過程,注意到這些瑣碎的操作都可用 bcc 工具處理。 ![](https://i.imgur.com/mbTo9Bt.png) 1. generate: 使用工具鏈如 clang, llvm 產生 eBPF bytecode 2. load: 掛載 eBPF 程式到對應的 hook point,這會經過一個 verifier,確保程式沒有迴圈、變數有正確初始化、沒有 unaligned access 的記憶體操作,最後由 BPF interpreter 執行 eBPF。 > 假設要掛載一個 eBPF 到 kprobe 上,首先 userspace 程式會呼叫 `perf_event_open(2)` 回傳一個 fd (file descriptor),這個 fd 再傳入 `ioctl(2)` 使用 PERF_EVENT_IOC_SET_BPF 參數加上要掛載的 eBPF 程式的 fd (由 `bpf(2)` 產生) 3. 輸出資料到 userspace (兩種方式) - perf_output: 利用 ftrace 框架的 ring buffer (/sys/kernel/debug/tracing/trace_pipe) 進行輸出,或者使用 perf ring buffer - async read: 存取 map ### eBPF Program Model ![](https://i.imgur.com/kq2oGxG.png) ## eBPF Examples 目前主要有兩種編寫以及掛載 eBPF 程式的工具,bcc 和 bpftrace。bcc 使用 python 為前端,而 bpftrace 則是使用自己的語法,有點類似 awk,筆者兩個工具都有使用過,而他們都有各自的優缺點 個人認為熟悉 bpftrace 的語法之後,可以讓你更快部屬 eBPF,適合做簡單的測試。而 bcc 能作到比 bpftrace 更精細的操作,如讀取 user-space 的資料,操作 `sk_buff` 等,但代價就是寫更多程式碼。 ### bcc `freq.py`: 紀錄 sendmsg 系統呼叫每秒發生的次數並印出 ```python #!/usr/bin/env python from bcc import BPF from time import sleep prog = """ BPF_TABLE("array", u32, u32, stats, 1); int hello_world(void *ctx) { u32 key = 0, value = 0, *val; val = stats.lookup_or_init(&key, &value); lock_xadd(val, 1); return 0; } """ b = BPF(text=prog) # getting shared kernel map stats_map = b.get_table("stats") execve_fnname = b.get_syscall_fnname("sendmsg") b.attach_kprobe(event=execve_fnname, fn_name="hello_world") for x in range(0, 20): stats_map[ stats_map.Key(0) ] = stats_map.Leaf(0) sleep(1) print "Total ", execve_fnname, " per second =", stats_map[ stats_map.Key(0) ].value; ``` `lock_xadd()` 是用來原子式寫入變數 > Since the defined array map is global, the accounting needs to use an atomic operation, which is defined as lock_xadd(). LLVM maps __sync_fetch_and_add() as a built-in function to the BPF atomic add instruction, that is, BPF_STX | BPF_XADD | BPF_W for word sizes. > > ref: https://docs.cilium.io/en/v1.7/bpf/ ### bpftrace 跟 `freq.py` 一樣計算並印出每秒呼叫 sendmsg 的次數 ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_sendmsg { @sendmsg = count(); } interval:s:1 { print(@sendmsg); clear(@sendmsg); }' ``` ## References perf: - Brendan Gregg FlameGraphs: http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html - Kernel Recipes 2017: Using Linux perf at Netflix: https://www.slideshare.net/brendangregg/kernel-recipes-2017-using-linux-perf-at-netflix?from_action= - Performance Monitoring Unit: http://rts.lab.asu.edu/web_438_2012/project_final/CSE_598_Performance_Monitoring_Unit.pdf - perf silde by brendan gregg: https://www.slideshare.net/brendangregg/scale2015-linux-perfprofiling/63 - A Study of Linux Perf and Slab Allocation Sub-Systems: https://core.ac.uk/download/pdf/144148979.pdf - https://s3.amazonaws.com/connect.linaro.org/yvr18/presentations/yvr18-416.pdf eBPF: - eBPF Basics: - https://www.slideshare.net/MichaelKehoe3/ebpf-basics-149201150 - https://elinux.org/images/d/dc/Kernel-Analysis-Using-eBPF-Daniel-Thompson-Linaro.pdf - eBPF/XDP: - http://evcomp.dcc.ufmg.br/wp-content/uploads/eBPF-XDP.pdf - https://www.netronome.com/blog/bpf-ebpf-xdp-and-bpfilter-what-are-these-things-and-what-do-they-mean-enterprise/ - Fast Packet Processing with eBPF and XDP: Concepts, Code, Challenges, and Applications: https://dl.acm.org/doi/fullHtml/10.1145/3371038#Bib0001 - KOSS slide