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 的步驟:

  1. 收集追蹤資料:我們可以使用 BPF 來收集程式的追蹤資料。例如,我們可以使用 perf 命令來收集程式的 CPU 使用情況。
  2. 處理追蹤資料:我們需要將收集到的追蹤資料進行處理,以便生成 Flame Graph。這包括將資料轉換為合適的格式,然後使用 FlameGraph 指令碼進行視覺化。

Histogram

Histogram 是一種統計圖表,用於展示資料的分佈情況。它可以幫助我們瞭解資料的集中趨勢和離散程度。要生成 Histogram,我們需要收集資料,然後使用 BPF 來進行統計和視覺化。

以下是生成 Histogram 的步驟:

  1. 收集資料:我們可以使用 BPF 來收集程式的資料。例如,我們可以使用 kprobes 來收集程式的指令延遲情況。
  2. 處理資料:我們需要將收集到的資料進行處理,以便生成 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。具體步驟如下:

  1. 使用Git下載Linux kernel的原始碼:

git clone https://github.com/torvalds/linux.git

2. 切換到Linux kernel原始碼目錄下的tools/bpf目錄
   ```bash
cd linux/tools/bpf
  1. 執行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,您可以從其官方倉函式庫中下載並編譯它。具體步驟如下:

  1. 使用Git下載BPFTrace的原始碼:

git clone https://github.com/iovisor/bpftrace.git

2. 切換到BPFTrace原始碼目錄:
   ```bash
cd bpftrace
  1. 執行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,您可以從其官方倉函式庫中下載並編譯它。具體步驟如下:

  1. 使用Git下載eBPF Exporter的原始碼:

git clone https://github.com/observability/ebpf_exporter.git

2. 切換到eBPF Exporter原始碼目錄
   ```bash
cd ebpf_exporter
  1. 執行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 技術已展現足夠成熟度,適合關注效能和安全的核心繫統採用。