目前perf 支援添加動态探測核心;通過 perf ,來自定義動态事件(perf probe),隻關注真正感興趣的事件。如下使用。
筆者有時需要使用perf 調試使用者态,so記錄之
靜态探針
是指事先在代碼中定義好,并編譯到應用程式或者核心中的探針。這些探針隻有在開啟探測功能時,才會被執行到;未開啟時并不會執行。常見的靜态探針包括核心中的跟蹤點(tracepoints)和 USDT(Userland Statically Defined Tracing)探針。
- USDT 探針,全稱是使用者級靜态定義跟蹤,需要在源碼中插入 DTRACE_PROBE() 代碼,并編譯到應用程式中。不過,也有很多應用程式内置了 USDT 探針,比如 MySQL、PostgreSQL 等。
動态探針
則是指沒有事先在代碼中定義,但卻可以在運作時動态添加的探針,比如函數的調用和傳回等。動态探針支援按需在核心或者應用程式中添加探測點,具有更高的靈活性。常見的動态探針有兩種,即用于核心态的 kprobes 和用于使用者态的 uprobes。
- kprobes 用來跟蹤核心态的函數,包括用于函數調用的 kprobe 和用于函數傳回的 kretprobe。
- uprobes 用來跟蹤使用者态的函數,包括用于函數調用的 uprobe 和用于函數傳回的 uretprobe。
注意,kprobes 需要核心編譯時開啟 CONFIG_KPROBE_EVENTS;而 uprobes 則需要核心編譯時開啟 CONFIG_UPROBE_EVENTS。
strace 基于系統調用 ptrace 實作
- 由于 ptrace 是系統調用,就需要在核心态和使用者态切換。當事件數量比較多時,繁忙的切換必然會影響原有服務的性能;
- ptrace 需要借助 SIGSTOP 信号挂起目标程序。這種信号控制和程序挂起,會影響目标程序的行為。
是以,在性能敏感的應用(比如資料庫)中,我并不推薦你用 strace (或者其他基于 ptrace 的性能工具)去排查和調試。
在 strace 的啟發下,結合核心中的 utrace 機制, perf 也提供了一個 trace 子指令,是取代 strace 的首選工具。相對于 ptrace 機制來說,perf trace 基于核心事件,自然要比程序跟蹤的性能好很多。perf trace 的使用方法如下所示,跟 strace 其實很像:
ftrace 和 perf 的功能已經比較豐富了,不過,它們有一個共同的缺陷,那就是不夠靈活,沒法像 DTrace 那樣通過腳本自由擴充。
BCC 把 eBPF 中的各種事件源(比如 kprobe、uprobe、tracepoint 等)和資料操作(稱為 Maps),也都轉換成了 Python 接口(也支援 lua)。這樣,使用 BCC 進行動态追蹤時,編寫簡單的腳本就可以了。
不過要注意,因為需要跟核心中的資料結構互動,真正核心的事件處理邏輯,還是需要我們用 C 語言來編寫。
SystemTap 也是一種可以通過腳本進行自由擴充的動态追蹤技術。在 eBPF 出現之前,SystemTap 是 Linux 系統中,功能最接近 DTrace 的動态追蹤機制。不過要注意,SystemTap 在很長時間以來都遊離于核心之外(而 eBPF 自誕生以來,一直根植在核心中)。
是以,從穩定性上來說,SystemTap 隻在 RHEL 系統中好用,在其他系統中則容易出現各種異常問題。當然,反過來說,支援 3.x 等舊版本的核心,也是 SystemTap 相對于 eBPF 的一個巨大優勢。
目前性能優化大師已經在linux4.x核心上給出例程!!6.5. Static User Tracing
Support was added in later 4.x series kernels. The following demonstrates Linux 4.10 (with an additional patchset), and tracing the Node.js USDT probes:
# perf buildid-cache --add `which node`
# perf list | grep sdt_node
sdt_node:gc__done [SDT event]
sdt_node:gc__start [SDT event]
sdt_node:http__client__request [SDT event]
sdt_node:http__client__response [SDT event]
sdt_node:http__server__request [SDT event]
sdt_node:http__server__response [SDT event]
sdt_node:net__server__connection [SDT event]
sdt_node:net__stream__end [SDT event]
# perf record -e sdt_node:http__server__request -a
^C[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.446 MB perf.data (3 samples) ]
# perf script
node 7646 [002] 361.012364: sdt_node:http__server__request: (dc2e69)
node 7646 [002] 361.204718: sdt_node:http__server__request: (dc2e69)
node 7646 [002] 361.363043: sdt_node:http__server__request: (dc2e69)
XXX fill me in, including how to use arguments.
If you are on an older kernel, say, Linux 4.4-4.9, you can probably get these to work with adjustments (I've even hacked them up with ftrace for older kernels), but since they have been in development, I haven't seen documentation outside of lkml, so you'll need to figure it out. (On this kernel range, you might find more documentation for tracing these with bcc/eBPF, including using the trace.py tool.)
參考此篇文章的udst 使用例程:usdt-on-linux
apt-get install systemtap-sdt-dev
#include <sys/sdt.h>
int main() {
DTRACE_PROBE(hello_usdt, enter);
int reval = 0;
return reval;
}
gcc ./hello-usdt.c -o ./hello-usdt
perf buildid-cache --add 'hello-usdt'
perf probe sdt_hello_usdt:enter
perf record -e sdt_hello_usdt:enter -ag