Hướng dẫn sử dụng bpftrace và BCC Tools trên CentOS Stream 9: Debug Hiệu Năng với eBPF

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

Server production của mình bắt đầu có triệu chứng lạ: load average bình thường, CPU không cao, nhưng response time tăng đột biến mỗi sáng lúc 7h. Mình ngồi nhìn top, htop, iostat -x 1 cả tiếng đồng hồ mà không tìm ra nguyên nhân. Traditional tools chỉ cho thấy snapshot — không cho thấy tại sao.

Chút context: mình đang quản lý nhóm server CentOS Stream 9 sau đợt migrate gấp từ CentOS 8 khi EOL — cùng thời điểm đó mình cũng chạy song song việc chuyển 5 server sang Rocky Linux, hết đúng 1 tuần. Đợt đó mình nhận ra vmstat và sar có thể cho thấy số đẹp trong khi hệ thống vẫn ì ra — không phải vì tool tệ, mà vì chúng chỉ đo tổng hợp. bpftrace và BCC mới cho thấy từng syscall, từng I/O request đang làm gì.

eBPF, BCC và bpftrace — Ba thứ này khác nhau như thế nào?

Ba thứ này hay bị dùng lẫn nhau. Phân biệt rõ từ đầu sẽ tiết kiệm khá nhiều thời gian tra documentation.

eBPF (extended Berkeley Packet Filter) là công nghệ cho phép chạy code trong kernel Linux mà không cần viết kernel module. Nói cụ thể hơn: bạn có thể instrument mọi thứ trong hệ thống — syscall, network packet, disk I/O, function call — với overhead cực thấp. eBPF verifier kiểm tra code trước khi nạp vào kernel, đảm bảo không crash hệ thống.

BCC (BPF Compiler Collection) là framework và tập hợp hơn 100 tool viết sẵn trên nền eBPF. Mỗi tool giải quyết một vấn đề cụ thể: biolatency cho disk I/O, execsnoop cho process execution, tcpconnect cho network connection. Bạn không cần biết eBPF internals để dùng BCC tools.

bpftrace là high-level tracing language với cú pháp giống awk/DTrace. Dùng khi bạn cần tùy chỉnh thứ BCC chưa có sẵn, hoặc muốn viết one-liner trace nhanh.

Quy tắc đơn giản: cần tool có sẵn → BCC. Cần custom → bpftrace.

Cài đặt trên CentOS Stream 9

# Cài BCC tools và Python binding
sudo dnf install -y bcc bcc-tools python3-bcc

# Cài bpftrace
sudo dnf install -y bpftrace

# Kernel headers — cần thiết nếu kernel chưa có BTF
sudo dnf install -y kernel-devel-$(uname -r) kernel-headers-$(uname -r)

# Kiểm tra BTF support (kernel >= 5.8 thường có sẵn)
ls /sys/kernel/btf/vmlinux && echo "BTF available" || echo "Need kernel headers"

Nếu có file /sys/kernel/btf/vmlinux, bạn không cần kernel headers — BTF-based tools chạy được ngay. CentOS Stream 9 với kernel 5.14+ thường đã có sẵn.

# Verify cài đặt thành công
bpftrace --version
ls /usr/share/bcc/tools/ | head -20

Thực hành: Debug các vấn đề hiệu năng thường gặp

Phân tích I/O latency với biolatency

Đây là thứ đầu tiên mình chạy khi nghi ngờ disk là thủ phạm. biolatency vẽ histogram latency phân bố theo microsecond:

# Monitor disk sda trong 10 giây
sudo /usr/share/bcc/tools/biolatency -d sda 10

# Phân tích I/O theo từng process
sudo /usr/share/bcc/tools/biosnoop -d sda

Output biolatency trông như sau:

     usecs               : count     distribution
         0 -> 1          : 1203     |********                        |
         2 -> 3          : 4521     |********************************|
         4 -> 7          : 2109     |***************                 |
         8 -> 15         : 892      |*******                         |
        16 -> 31         : 45       |                                |
     32768 -> 65535      : 12       |                                | <-- spike bất thường

Bucket cuối đó — 12 request mất 32–65ms — là dấu hiệu có vấn đề. Chạy thêm biosnoop sẽ chỉ ra chính xác process nào gây ra.

Theo dõi process execution với execsnoop

Hệ thống fork process bí ẩn? execsnoop capture mọi exec() syscall trong realtime:

sudo /usr/share/bcc/tools/execsnoop

Output mẫu:

PCOMM            PID    PPID   RET ARGS
bash             12345  12344    0 /bin/bash -c /usr/local/bin/check-disk.sh
python3          12346  12345    0 python3 /usr/local/bin/check-disk.sh

Chính nhờ tool này mình tìm ra nguyên nhân latency mỗi sáng: một script backup cũ chạy mỗi phút, fork 15–20 subprocess, tranh giành I/O với web server. Không ai nhớ script đó còn tồn tại.

Debug file open calls với opensnoop

# Xem tất cả file được open (kèm return code)
sudo /usr/share/bcc/tools/opensnoop

# Chỉ theo dõi process nginx
sudo /usr/share/bcc/tools/opensnoop -n nginx

# Lọc theo PID cụ thể
sudo /usr/share/bcc/tools/opensnoop -p 1234

Hay dùng để debug lỗi “file not found” ở app level, hoặc phát hiện app đang đọc config từ đường dẫn không ngờ tới — chuyện này hay xảy ra sau migration khi path cũ vẫn hardcode trong config.

Monitor TCP connections với tcpconnect

# Xem outbound TCP connections
sudo /usr/share/bcc/tools/tcpconnect

# Chỉ filter port database và cache
sudo /usr/share/bcc/tools/tcpconnect -P 5432,6379,3306

Mình đã gặp case này: sau migration database, app vẫn âm thầm connect tới IP cũ — không có lỗi rõ ràng vì connection timeout chậm, chỉ thấy latency tăng bất thường. tcpconnect lộ ra ngay trong 10 giây đầu chạy.

CPU profiling không cần restart process

# Sample CPU stack traces trong 30 giây, 99 Hz
sudo /usr/share/bcc/tools/profile -F 99 30 > /tmp/profile.out

# Profile một PID cụ thể
sudo /usr/share/bcc/tools/profile -F 99 -p 1234 30

Output là folded stack trace, tương thích với flamegraph.pl để visualize. Đây là cách debug CPU bottleneck hiệu quả nhất mà không cần recompile binary với debug symbols.

bpftrace: Tracing tùy chỉnh theo nhu cầu

One-liner thường dùng

# Đếm syscall theo tên, in ra khi nhấn Ctrl+C
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }'

# Đo latency của read() syscall — histogram nanosecond
sudo bpftrace -e '
  tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; }
  tracepoint:syscalls:sys_exit_read  /@start[tid]/ {
    @latency_ns = hist(nsecs - @start[tid]);
    delete(@start[tid]);
  }'

# Top 10 file được open nhiều nhất
sudo bpftrace -e '
  tracepoint:syscalls:sys_enter_openat { @[str(args->filename)] = count(); }
  END { print(@, 10); }'

Script trace slow disk writes

#!/usr/bin/env bpftrace
// Chạy: sudo bpftrace slow_writes.bt
// Báo cáo các ext4 write mất > 10ms

kprobe:ext4_file_write_iter
{
    @start[tid] = nsecs;
}

kretprobe:ext4_file_write_iter
/@start[tid]/
{
    $dur_ms = (nsecs - @start[tid]) / 1000000;
    if ($dur_ms > 10) {
        printf("[SLOW WRITE] PID=%d COMM=%s duration=%dms\n",
               pid, comm, $dur_ms);
    }
    delete(@start[tid]);
}

Trace function latency với kprobe

# Đo latency VFS read operations
sudo bpftrace -e '
  kprobe:vfs_read    { @start[tid] = nsecs; }
  kretprobe:vfs_read /@start[tid]/ {
    @us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
  }'

Lưu ý khi dùng trên production

  • Overhead: eBPF overhead rất thấp nhưng không phải zero. Tránh trace hot path (mọi packet, mọi syscall) liên tục — dùng interval hoặc filter theo PID/process.
  • Compile delay: BCC tools compile BPF program khi khởi chạy, có thể mất 1–5 giây. Bình thường, không phải lỗi.
  • Quyền truy cập: Cần root hoặc capabilities CAP_BPF + CAP_PERFMON.
  • Kernel version: Một số feature yêu cầu kernel >= 5.8. CentOS Stream 9 đủ điều kiện với kernel 5.14+.

Thực tế mình chưa thấy overhead đáng kể khi chạy các BCC tools này 15–30 giây để debug rồi tắt đi. Vấn đề chỉ xảy ra nếu để một số tool chạy liên tục nhiều giờ trên hệ thống tải cao mà không filter.

Kết luận

Trước đây cùng loại vấn đề này tốn 2–3 ngày debug. Với bpftrace và BCC, mình xác định được trong vài phút — không phải vì tool thần kỳ, mà vì thay vì đoán từ metric tổng hợp, bạn nhìn thẳng vào kernel đang làm gì.

Cách bắt đầu thực tế nhất: chạy biolatency và execsnoop trên test server, xem output thực tế, rồi từ đó tìm tool phù hợp với vấn đề bạn đang gặp. BCC trước, bpftrace sau — cú pháp bpftrace đơn giản hơn nhiều so với viết eBPF C trực tiếp, và khi đã có nền tảng BCC sẽ quen ngay. Tài liệu tốt nhất: bpftrace reference guide trên GitHub và cuốn BPF Performance Tools của Brendan Gregg — ông này là tác giả của phần lớn BCC tools bạn vừa dùng.

Share: