---
# System prepended metadata

title: 'Performance Evaluation: perf, eBPF'
tags: [Profiling]

---





###### 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

