Linux 提供了使用者靜態定義追蹤點(USDT),讓開發者能在程式碼中插入追蹤點,實作更精細的效能分析。USDT 具有低開銷、跨語言支援以及適用於生產環境等優點。開發者只需在程式碼中插入特定指令即可定義追蹤點,並使用 readelf 或 BCC 的 tplist 工具發現這些追蹤點。接著,可以利用 BCC 將 BPF 程式附加到 USDT,實作更進階的追蹤功能。透過 BPF 程式,可以收集程式執行過程中的各種資訊,例如函式呼叫、系統呼叫、網路封包等,並將這些資訊用於效能分析和除錯。此外,文章還介紹瞭如何使用 Flame Graph 和 Histogram 等視覺化工具,將收集到的效能資料以更直觀的方式呈現,幫助開發者快速識別效能瓶頸。
使用者空間追蹤的挑戰
使用者空間追蹤比核心空間追蹤更具挑戰性,因為使用者空間的程式碼是由應用程式開發人員編寫的,且可能會發生變化。因此,使用者空間追蹤需要更高的靈活性和可擴充套件性。
使用者靜態定義追蹤點(USDT)
為瞭解決使用者空間追蹤的挑戰,Linux 引入了使用者靜態定義追蹤點(USDT)。USDT 提供了一種靜態的方式來定義追蹤點,使得應用程式開發人員可以在程式碼中插入追蹤點。
USDT 的優點
USDT 有以下優點:
- 提供了一種低開銷的方式來插入追蹤點
- 可以用於任何程式語言
- 可以用於生產環境
USDT 的實作
USDT 的實作相對簡單。開發人員只需在程式碼中插入一條特殊的指令,即可定義一個追蹤點。例如:
#include <sys/sdt.h>
int main() {
DTRACE_PROBE("hello-usdt", "probe-main");
}
在這個例子中,我們定義了一個名為 hello-usdt
的追蹤點,該點有一個名為 probe-main
的探測器。
發現 USDT
要使用 USDT,需要先發現可用的追蹤點。有一種方法是使用 readelf
工具來顯示 ELF 檔案中的資訊。例如:
readelf -n./hello_usdt
這將顯示 hello_usdt 檔案中定義的追蹤點。
另一種方法是使用 BCC 的 tplist
工具來顯示可用的追蹤點。例如:
tplist -l./hello_usdt
這將顯示 hello_usdt 檔案中定義的追蹤點。
附加 BPF 程式到 USDT
一旦發現了可用的追蹤點,就可以附加 BPF 程式到這些點。例如:
from bcc import BPF, USDT
bpf_source = """
#include <uapi/linux/ptrace.h>
int trace_binary_exec(struct pt_regs *ctx) {
u64 pid = bpf_get_current_pid_tgid();
bpf_trace_printk("New hello_usdt process running with PID: %d", pid);
}
"""
usdt = USDT(path = "./hello_usdt")
usdt.enable_probe(probe = "probe-main", fn_name = "trace_binary_exec")
bpf = BPF(text = bpf_source, usdt = usdt)
bpf.trace_print()
在這個例子中,我們定義了一個 BPF 程式,該程式將被附加到 probe-main
追蹤點。當 probe-main
追蹤點被觸發時,BPF 程式將被執行,並列印預出一個訊息。
使用 BPF 來追蹤程式執行
在本文中,我們將使用 BPF(Berkeley Packet Filter)來追蹤程式的執行。BPF是一種強大的工具,允許我們在核心層級上監控和分析系統的行為。
初始化 BPF 環境
首先,我們需要初始化 BPF 環境。這涉及到建立一個 BPF 程式,並將其附加到我們想要追蹤的程式上。在這個例子中,我們將使用 bcc
工具來建立和載入 BPF 程式。
from bcc import BPF
# 載入 BPF 程式
bpf = BPF(src_file="trace_program.c")
定義追蹤點
接下來,我們需要定義追蹤點。追蹤點是指程式中特定的位置,我們想要在那裡收集資料。在這個例子中,我們將使用 USDT
(User-Level Statically Defined Tracing)來定義追蹤點。
# 定義追蹤點
bpf.attach_uprobe(name="c:ruby", sym="rb_f_find", fn_name="trace_latency")
收集資料
現在,我們可以收集資料了。當程式執行到我們定義的追蹤點時,BPF 程式將會被觸發,並收集相關資料。
# 收集資料
bpf.trace_print()
處理資料
最後,我們需要處理收集到的資料。在這個例子中,我們將使用 bcc
工具來處理資料,並將其列印預出來。
# 處理資料
print(bpf.trace_print())
Ruby 靜態追蹤
除了使用 BPF 來追蹤程式執行外,我們還可以使用 Ruby 靜態追蹤來收集資料。Ruby 靜態追蹤是一種工具,允許我們在 Ruby 程式中定義追蹤點,並收集相關資料。
require 'ruby-static-tracing'
# 定義追蹤點
config.add_tracer(StaticTracing::Tracer::Latency)
# 收集資料
class UserModel
def find(id)
#...
end
include StaticTracing::Tracer::Concerns::Latency
end
使用 BCC 來提取資料
最後,我們可以使用 bcc
工具來提取資料。bcc
工具提供了一個簡單的方式來收集和處理資料。
# 使用 BCC 來提取資料
bpf_source = """
#include <uapi/linux/ptrace.h>
int trace_latency(struct pt_regs *ctx) {
char method[64];
u64 latency;
bpf_usdt_readarg_p(1, ctx, &method, sizeof(method));
bpf_usdt_readarg(2, ctx, &latency);
bpf_trace_printk("method %s took %d ms", method, latency);
}
"""
# 載入 BPF 程式
bpf = BPF(src_file="trace_program.c")
# 提取資料
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--pid", type=int, help="Process ID")
args = parser.parse_args()
bpf.attach_uprobe(name="c:ruby", sym="rb_f_find", fn_name="trace_latency")
使用BPF進行追蹤和視覺化
在上一節中,我們探討瞭如何使用BPF進行追蹤和分析應用程式的行為。在本文中,我們將更深入地探討如何使用BPF進行視覺化和分析。
Flame Graphs
Flame Graphs是一種視覺化工具,幫助您瞭解系統如何花費時間。它們可以提供一個清晰的表示,哪些程式碼在應用程式中被執行得更頻繁。Brendan Gregg,Flame Graphs的創造者,在GitHub上維護了一套指令碼,以便輕鬆生成這些視覺化格式。
BPF_STACK_TRACE
BCC提供了幾個工具來幫助您聚合和視覺化堆積疊追蹤,其中最重要的是BPF_STACK_TRACE
巨集。這個巨集生成了一個BPF對映,型別為BPF_MAP_TYPE_STACK_TRACE
,用於儲存BPF程式積累的堆積疊。這個BPF對映還增強了方法,以從程式的上下文中提取堆積疊資訊,並在您想要使用它們時遍歷聚合的堆積疊追蹤。
範例:簡單的BPF Profiler
以下範例中,我們構建了一個簡單的BPF profiler,它從使用者空間應用程式中收集堆積疊追蹤,並生成on-CPU flame graphs。為了測試這個profiler,我們將撰寫一個最小的Go程式,生成CPU負載。
package main
import "time"
func main() {
j := 3
for time.Since(time.Now()) < time.Second {
for i := 1; i < 1000000; i++ {
j *= i
}
}
}
BPF程式
以下是BPF程式的第一部分,初始化profiler結構:
bpf_source = """
#include <uapi/linux/ptrace.h>
#include <uapi/linux/bpf_perf_event.h>
#include <linux/sched.h>
struct trace_t {
int stack_id;
}
BPF_HASH(cache, struct trace_t);
BPF_STACK_TRACE(traces, 10000);
使用BPF進行程式碼追蹤
初始化資料結構
為了儲存每個堆積疊框架的參考識別符,我們初始化了一個結構。這些識別符稍後將用於確定哪個程式碼路徑正在被執行。
初始化BPF雜湊對映
我們使用BPF雜湊對映來聚合相同堆積疊框架的出現次數。 Flame Graph指令碼使用這個聚合值來確定程式碼的執行頻率。
初始化BPF堆積疊追蹤對映
我們設定了堆積疊追蹤對映的最大大小,但這個值可以根據需要進行調整。由於我們的Go應用程式不是很大,因此10,000個元素足夠。
實作堆積疊追蹤聚合函式
int collect_stack_traces(struct bpf_perf_event_data *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
if (pid!= PROGRAM_PID)
return 0;
struct trace_t trace = {
.stack_id = traces.get_stackid(&ctx->regs, BPF_F_USER_STACK)
};
cache.increment(trace);
return 0;
}
這個函式聚合堆積疊追蹤並將其儲存到緩衝區中。
驗證程式ID
我們驗證當前BPF上下文中的程式ID是否與我們的Go應用程式匹配。如果不匹配,我們忽略該事件。
建立追蹤並聚合其使用情況
我們使用get_stackid
函式從程式上下文中取得堆積疊ID,並使用BPF_F_USER_STACK
標誌指示我們只關心使用者空間應用程式。
增加追蹤計數器
我們增加追蹤計數器以跟蹤相同程式碼的執行次數。
附加堆積疊追蹤收集器
program_pid = int(sys.argv[0])
bpf_source = bpf_source.replace('PROGRAM_PID', program_pid)
bpf = BPF(text=bpf_source)
bpf.attach_perf_event(ev_type=PerfType.SOFTWARE,
ev_config=PerfSWConfig.CPU_CLOCK,
fn_name='collect_stack_traces')
我們附加堆積疊追蹤收集器到所有軟體Perf事件中,並組態BPF程式使用CPU時鐘作為時間源。
Dump堆積疊追蹤
try:
sleep(99999999)
except KeyboardInterrupt:
signal.signal(signal.SIGINT, signal_ignore)
for trace, acc in sorted(cache.items(), key=lambda cache: cache[1].value):
line = []
if trace.stack_id < 0 and trace.stack_id == -errno.EFAULT:
line = ['Unknown stack']
#...
當收到中斷訊號時,我們Dump堆積疊追蹤到標準輸出中。
使用 BPF 進行效能分析和視覺化
在上一節中,我們探討瞭如何使用 BPF 來進行追蹤和視覺化。現在,我們將更深入地瞭解如何使用 BPF 來進行效能分析和視覺化。
Flame Graph
Flame Graph 是一種視覺化工具,用於展示程式的效能瓶頸。它可以幫助我們瞭解程式的執行時間和記憶體使用情況。要生成 Flame Graph,我們需要收集程式的追蹤資料,然後使用 FlameGraph 指令碼進行視覺化。
以下是生成 Flame Graph 的步驟:
- 收集追蹤資料:我們可以使用 BPF 來收集程式的追蹤資料。例如,我們可以使用
perf
命令來收集程式的 CPU 使用情況。 - 處理追蹤資料:我們需要將收集到的追蹤資料進行處理,以便生成 Flame Graph。這包括將資料轉換為合適的格式,然後使用 FlameGraph 指令碼進行視覺化。
Histogram
Histogram 是一種統計圖表,用於展示資料的分佈情況。它可以幫助我們瞭解資料的集中趨勢和離散程度。要生成 Histogram,我們需要收集資料,然後使用 BPF 來進行統計和視覺化。
以下是生成 Histogram 的步驟:
- 收集資料:我們可以使用 BPF 來收集程式的資料。例如,我們可以使用
kprobes
來收集程式的指令延遲情況。 - 處理資料:我們需要將收集到的資料進行處理,以便生成 Histogram。這包括將資料轉換為合適的格式,然後使用 BPF 來進行統計和視覺化。
BPF 程式
要生成 Flame Graph 和 Histogram,我們需要編寫 BPF 程式。以下是示例 BPF 程式:
#include <uapi/linux/ptrace.h>
BPF_HASH(cache, u64, u64);
BPF_HISTOGRAM(histogram);
int trace_bpf_prog_load_start(void *ctx) {
u64 pid = bpf_get_current_pid_tgid();
u64 start_time_ns = bpf_ktime_get_ns();
cache.update(&pid, &start_time_ns);
return 0;
}
這個 BPF 程式使用 BPF_HASH
來儲存程式的初始時間,然後使用 BPF_HISTOGRAM
來生成 Histogram。
BPF 程式設計與效能分析
介紹
BPF(Berkeley Packet Filter)是一種強大的程式設計工具,允許開發人員在 Linux 核心中執行自定義程式。BPF 可以用於各種應用,包括網路封包過濾、系統呼叫追蹤和效能分析。在本文中,我們將探討如何使用 BPF 進行效能分析和視覺化。
BPF 程式設計
BPF 程式設計涉及編寫 C 程式碼並使用 BPF API 來與 Linux 核心進行互動。以下是一個簡單的 BPF 程式範例:
int trace_bpf_prog_load_return(void *ctx) {
u64 *start_time_ns, delta;
u64 pid = bpf_get_current_pid_tgid();
start_time_ns = cache.lookup(&pid);
if (start_time_ns == 0)
return 0;
delta = bpf_ktime_get_ns() - *start_time_ns;
histogram.increment(bpf_log2l(delta));
return 0;
}
這個程式使用 BPF API 來追蹤 bpf_prog_load
系統呼叫的執行時間,並將結果儲存在一個 histogram 中。
效能分析
BPF 可以用於各種效能分析任務,包括:
- 系統呼叫追蹤:BPF 可以用於追蹤系統呼叫的執行時間和頻率。
- 網路封包分析:BPF 可以用於分析網路封包的內容和執行時間。
- 程式執行時間分析:BPF 可以用於分析程式的執行時間和頻率。
Perf 事件
Perf 事件是 BPF 中的一種機制,允許開發人員在使用者空間中處理 BPF 事件。Perf 事件可以用於各種應用,包括效能分析和視覺化。
以下是一個使用 Perf 事件的範例:
bpf_source = """
#include <uapi/linux/ptrace.h>
BPF_PERF_OUTPUT(events);
int do_sys_execve(struct pt_regs *ctx, void *filename, void *argv, void *envp) {
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
events.perf_submit(ctx, &comm, sizeof(comm));
return 0;
}
"""
bpf = BPF(text=bpf_source)
execve_function = bpf.get_syscall_fnname("execve")
bpf.attach_kprobe(event=execve_function, fn_name="do_sys_execve")
這個範例使用 Perf 事件來追蹤 execve
系統呼叫的執行時間和頻率,並將結果儲存在一個 histogram 中。
內容解密:
- BPF 程式設計涉及編寫 C 程式碼並使用 BPF API 來與 Linux 核心進行互動。
- Perf 事件是 BPF 中的一種機制,允許開發人員在使用者空間中處理 BPF 事件。
- BPF 可以用於各種效能分析任務,包括系統呼叫追蹤、網路封包分析和程式執行時間分析。
圖表翻譯:
flowchart TD A[開始] --> B[編寫 BPF 程式] B --> C[編譯 BPF 程式] C --> D[載入 BPF 程式] D --> E[附加 BPF 程式到 kprobe] E --> F[收集效能資料] F --> G[視覺化效能資料]
這個流程圖示範瞭如何使用 BPF 進行效能分析和視覺化。首先,編寫 BPF 程式並編譯它。然後,載入 BPF 程式並附加它到 kprobe。接下來,收集效能資料並視覺化它。
BPF工具與實用應用
在前幾章中,我們探討瞭如何使用BPF(Berkeley Packet Filter)來開發自定義的系統追蹤工具。然而,除了自己動手開發工具外,還有許多現成的BPF工具可供使用。這些工具可以幫助您更有效地進行系統追蹤和效能分析。
BPFTool
BPFTool是一個用於檢視和管理BPF程式和對映的命令列工具。它允許您從終端觀察和修改BPF程式和對映的行為。要使用BPFTool,您需要從Linux kernel的原始碼倉函式庫中下載並編譯它。
安裝BPFTool
要安裝BPFTool,您需要下載Linux kernel的原始碼,並從中編譯BPFTool。具體步驟如下:
- 使用Git下載Linux kernel的原始碼:
git clone https://github.com/torvalds/linux.git
2. 切換到Linux kernel原始碼目錄下的tools/bpf目錄:
```bash
cd linux/tools/bpf
- 執行make指令編譯BPFTool:
make
4. 安裝編譯好的BPFTool:
```bash
sudo make install
使用BPFTool
安裝完成後,您可以使用BPFTool來檢視和管理BPF程式和對映。例如,您可以使用以下指令來列出所有正在執行的BPF程式:
bpftool prog list
或者,您可以使用以下指令來檢視某個特定BPF程式的詳細資訊:
bpftool prog show <prog_id>
替換 <prog_id>
為您想要檢視的BPF程式的ID。
BPFTrace
BPFTrace是一個用於撰寫BPF程式的高階工具。它提供了一個簡潔的域特定語言(DSL),使您可以更容易地撰寫BPF程式。BPFTrace支援多種輸出格式,包括文字、JSON和圖形。
安裝BPFTrace
要安裝BPFTrace,您可以從其官方倉函式庫中下載並編譯它。具體步驟如下:
- 使用Git下載BPFTrace的原始碼:
git clone https://github.com/iovisor/bpftrace.git
2. 切換到BPFTrace原始碼目錄:
```bash
cd bpftrace
- 執行make指令編譯BPFTrace:
make
4. 安裝編譯好的BPFTrace:
```bash
sudo make install
使用BPFTrace
安裝完成後,您可以使用BPFTrace來撰寫和執行BPF程式。例如,您可以使用以下指令來執行一個簡單的BPF程式:
bpftrace -e 'tracepoint:syscalls:sys_enter { printf("%s\n", comm); }'
這個程式會追蹤所有系統呼叫,並列印預出呼叫程式的名稱。
eBPF Exporter
eBPF Exporter是一個開源專案,旨在將BPF與Prometheus整合起來。它允許您使用BPF程式收集系統 metrics,並將其匯出到Prometheus中。
安裝eBPF Exporter
要安裝eBPF Exporter,您可以從其官方倉函式庫中下載並編譯它。具體步驟如下:
- 使用Git下載eBPF Exporter的原始碼:
git clone https://github.com/observability/ebpf_exporter.git
2. 切換到eBPF Exporter原始碼目錄:
```bash
cd ebpf_exporter
- 執行make指令編譯eBPF Exporter:
make
4. 安裝編譯好的eBPF Exporter:
```bash
sudo make install
使用eBPF Exporter
安裝完成後,您可以使用eBPF Exporter來收集系統 metrics,並將其匯出到Prometheus中。例如,您可以使用以下指令來啟動eBPF Exporter:
ebpf_exporter -c /path/to/config.yaml
替換 /path/to/config.yaml
為您的組態檔案路徑。
使用 BPFTool 探索 Linux 系統的 BPF 功能
在探索 Linux 系統的 BPF(Berkeley Packet Filter)功能時,BPFTool 是一個非常有用的工具。以下是使用 BPFTool 的步驟:
從底層實作到高階應用的全面檢視顯示,BPF 技術在系統追蹤、效能分析和安全監控等領域展現了強大的能力。本文深入探討了使用者靜態定義追蹤點(USDT)、BPF 程式設計、效能分析方法以及相關工具如 BPFTool、BPFTrace 和 eBPF Exporter 的使用方法。透過結合 BPF 程式設計和各種工具,開發者可以更有效地監控系統行為、診斷效能瓶頸並提升系統安全性。BPF 技術的發展仍在持續演進,未來將會出現更多功能強大的工具和應用場景。對於追求高效能和安全性的系統管理員和開發者而言,深入理解和應用 BPF 技術至關重要。玄貓認為,BPF 技術已展現足夠成熟度,適合關注效能和安全的核心繫統採用。