커널 내부에서 어떤 프로세스가 어떤 파일을 열고, 어떤 시스템 콜을 얼마나 호출하는지 확인하고 싶을 때 strace는 대상 프로세스 하나에 붙어야 하고 오버헤드도 커서 운영 중인 서버에서 시스템 전체를 훑어보기엔 부담스럽다. BCC(BPF Compiler Collection)는 이런 상황을 위한 eBPF 기반 도구 모음으로, 커널 소스 재컴파일 없이 opensnoop·execsnoop·syscount 같은 완성된 트레이싱 도구를 바로 실행할 수 있다. 이 글에서는 Ubuntu에 BCC를 설치하고 대표 도구 몇 개를 실제로 돌려본 결과, 그리고 실행 중 만난 컴파일 실패 사례와 그 대안을 정리한다.
BCC 설치
Ubuntu 패키지는 도구 이름 뒤에 -bpfcc 접미사가 붙는다. 실행 중인 커널과 정확히 일치하는 linux-headers도 함께 필요하다.
$ sudo apt install bpfcc-tools linux-headers-$(uname -r)
$ dpkg -L bpfcc-tools | grep /usr/sbin | head -5
/usr/sbin/argdist-bpfcc
/usr/sbin/bashreadline-bpfcc
/usr/sbin/bindsnoop-bpfcc
/usr/sbin/biolatency-bpfcc
/usr/sbin/biolatpcts-bpfcc
대표 도구로 실시간 이벤트 확인
opensnoop-bpfcc는 시스템 전체의 open() 호출을 실시간으로 보여준다. 백그라운드 데몬(systemd-oomd 등)이 cgroup 파일을 폴링하는 노이즈는 걸러내고, 실제로 만든 이벤트만 추려봤다.
$ sudo opensnoop-bpfcc
PID COMM FD ERR PATH
5311 cat 3 0 /etc/hostname
5312 touch 3 0 /tmp/bcc_open_demo
syscount-bpfcc는 일정 시간 동안 호출된 시스템 콜을 종류별로 집계한다.
$ sudo syscount-bpfcc -d 3
Tracing syscalls, printing top 10... Ctrl+C to quit.
[00:07:57]
SYSCALL COUNT
unlinkat 212
nanosleep 48
close 20
fstat 19
epoll_pwait 19
openat 16
futex 16
recvmsg 14
ioctl 14
cachestat-bpfcc는 페이지 캐시 히트/미스와 dirty 페이지 발생을 초 단위로 보여준다. 20MB 파일을 쓰는 동안 관찰한 결과다.
$ sudo cachestat-bpfcc 1 4
HITS MISSES DIRTIES HITRATIO BUFFERS_MB CACHED_MB
0 0 7707 0.00% 49 2016
0 0 0 0.00% 49 2016
쓰기 직후 같은 파일을 바로 읽었는데도 MISSES가 늘지 않은 이유는, 방금 쓴 페이지가 이미 페이지 캐시에 dirty 상태로 남아 있어 디스크까지 다시 갈 필요 없이 그대로 히트 처리됐기 때문이다.
기존 CLI 도구로 부족하면 BCC의 Python API로 직접 BPF 프로그램을 작성할 수도 있다. 아래는 openat() 호출을 PID별로 5초간 집계하는 스크립트다.
from bcc import BPF
from time import sleep
bpf_text = """
BPF_HASH(counts, u32, u64);
int trace_openat(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 zero = 0, *val;
val = counts.lookup_or_try_init(&pid, &zero);
if (val) {
(*val)++;
}
return 0;
}
"""
b = BPF(text=bpf_text)
b.attach_kprobe(event=b.get_syscall_fnname("openat"), fn_name="trace_openat")
sleep(5)
for k, v in sorted(b["counts"].items(), key=lambda kv: kv[1].value, reverse=True)[:10]:
print("%-8d %-d" % (k.value, v.value))실행 결과, 5초 동안 백그라운드 데몬을 포함한 각 PID의 openat 호출 횟수가 그대로 잡혔다.
$ sudo python3 count_open.py
PID OPENAT_COUNT
935 36
3269 16
974 9
3268 8
3267 5
완성된 도구에 원하는 지표가 없으면 이렇게 커널 함수 하나에 kprobe를 붙이고 맵(BPF_HASH)에 집계하는 식으로 필요한 만큼만 짜서 쓸 수 있다.
BCC에는 이 밖에도 영역별로 다양한 도구가 있다.
| 영역 | 도구 예시 | 용도 |
|---|---|---|
| 파일/디스크 | filetop-bpfcc, ext4slower-bpfcc, biotop-bpfcc | 파일 R/W 상위 프로세스, 느린 I/O 추적 |
| 네트워크 | tcpconnect-bpfcc, tcplife-bpfcc | TCP 연결 시작/종료 추적 |
| 스케줄러 | runqlat-bpfcc, cpudist-bpfcc | 런큐 대기시간, CPU 사용 분포 |
| 메모리 | oomkill-bpfcc, memleak-bpfcc | OOM 발생 추적, 메모리 누수 의심 구간 탐지 |
같은 환경(Ubuntu 24.04, 커널 7.0.0-28-generic)에서 execsnoop-bpfcc, biolatency-bpfcc는 아래처럼 컴파일 단계에서 실패했다.
| 도구 | 용도 | 결과 |
|---|---|---|
| opensnoop-bpfcc | open() 호출 추적 | 정상 동작 |
| syscount-bpfcc | 시스템 콜 집계 | 정상 동작 |
| execsnoop-bpfcc | 프로세스 실행(exec) 추적 | 컴파일 실패 |
| biolatency-bpfcc | 블록 I/O 지연 히스토그램 | 컴파일 실패 |
주의사항: 컴파일 실패와 bpftrace 대안
BCC 도구는 실행할 때마다 내장된 C 소스를 clang으로 커널 헤더 기준에 맞춰 컴파일한다. execsnoop-bpfcc는 struct filename을 다루는 헤더를 포함하는 과정에서 다음 에러로 즉시 실패했다.
$ sudo execsnoop-bpfcc
In file included from /virtual/main.c:16:
include/linux/fs.h:2431:15: error: static assertion failed due to
requirement 'sizeof(struct filename) % 64 == 0'
2431 | static_assert(sizeof(struct filename) % 64 == 0);
Exception: Failed to compile BPF module <text>
설치된 linux-headers는 uname -r과 버전까지 정확히 일치했지만 실패했다.
$ uname -r
7.0.0-28-generic
$ apt list --installed 2>/dev/null | grep linux-headers
linux-headers-7.0.0-28-generic/noble-updates,now 7.0.0-28.28~24.04.1 amd64
linux-headers-generic-hwe-24.04/noble-updates,now 7.0.0-28.28~24.04.1 amd64
버전은 맞아도 실제로 부팅된 커널 바이너리의 구조체 레이아웃이 헤더만으로 재현되지 않는 경우가 있고, BCC는 이럴 때 컴파일 자체가 막힌다. 이런 상황에서는 커널의 BTF(BPF Type Format) 정보를 그대로 읽어 매번 컴파일하지 않는 bpftrace가 더 안정적으로 동작했다.
$ sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
printf("%d %s %s\n", pid, comm, str(args.filename));
}'
Attaching 1 probe...
4652 bash /usr/bin/ls
4586 bash /usr/bin/sleep
execsnoop-bpfcc가 실패한 것과 같은 exec 추적을 bpftrace 한 줄로 대체한 것이다. BCC 도구가 컴파일 에러로 죽으면 재빌드나 커널 옵션을 뒤지기 전에 bpftrace로 같은 이벤트를 추적할 수 있는지부터 확인하는 게 빠르다.
마무리
BCC는 완성된 도구를 바로 쓸 수 있다는 게 장점이지만, 매번 커널 헤더 기준으로 컴파일하는 구조라 커널 버전이나 빌드 옵션에 따라 특정 도구만 깨질 수 있다. 하나가 실패했다고 eBPF 트레이싱 자체를 포기하지 말고 bpftrace 같은 BTF 기반 대안을 함께 준비해두는 편이 실무에서 더 안정적이다.
참고
iovisor/bcc GitHub
bpftrace GitHub
Brendan Gregg – eBPF Tools