bpftrace
/ BEE-pee-EFF-trayss /
手寫一支 eBPF 程式——用受限的 C、編譯它、載入它、接好 maps、再把結果讀回來——對一個像「現在哪些行程正在開檔案?」這樣的快速問題而言,是太多的繁文縟節了。bpftrace 就是那條高階捷徑:一個受老工具 awk 啟發的小型腳本語言,讓你用一行就表達出一支追蹤程式,並在底層替你把所有 eBPF 的水電工程都做掉。
具體來說,一段 bpftrace 腳本是一串「probe { action }」規則,非常接近 awk 的「樣式—動作」風格。probe 指名要掛在哪——例如「tracepoint:syscalls:sys_enter_openat」在 openat 系統呼叫上觸發,或「kprobe:vfs_read」在一個核心函式上觸發,或「uprobe:/bin/bash:readline」在一個使用者程式內部觸發。action 是觸發時要做的事:你可以讀引數、累積到寫成 @name 的關聯陣列裡、用 hist() 與 count() 之類的內建函式建直方圖、並印出結果。bpftrace 把那段腳本編譯成 eBPF 位元碼,讓驗證器檢查它,載入核心,管理那些 BPF maps,並在你按 Ctrl-C 時印出彙總輸出。BCC(BPF Compiler Collection)是相關的較低階工具組:一套函式庫,加上一大批現成、以 Python 驅動的工具(execsnoop、biolatency、tcpconnect 等等),在你需要比一行指令更多控制時使用。
它之所以重要,是因為它正是讓 eBPF 對日常正式環境可觀測性變得實用的東西:工程師能用一行、以低負擔、且不重啟任何東西,去回答一個關於活系統的精確臨時問題。誠實的提醒:bpftrace 一行指令仍然是一支 eBPF 程式,所以它必須通過驗證器,而它的彙總存在有大小上限的 maps 裡;追蹤非常高頻的事件(每個封包、每次記憶體配置)會加入實質負擔,重負載下甚至會丟事件;而它通常需要 root 或特定權能,所以它是一個要在正式環境機器上小心揮舞的強力工具。
# 全系統 read() 大小的直方圖,即時: $ bpftrace -e 'tracepoint:syscalls:sys_enter_read { @bytes = hist(args->count); }' # @bytes: # [16, 32) 412 |@@@@@@ # [4K, 8K) 8841 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
一行 bpftrace 程式即時建出 read() 大小的直方圖;冗長的 eBPF 編譯、載入與 map 管理都被藏起來了。
bpftrace 是一個前端,不是另一種技術:每段腳本都會變成一支由驗證器檢查、核心執行的 eBPF 程式,所以它的安全保證與限制,就「正是」eBPF 的那一套。