最近公開されたスライドでLinuxのパフォーマンスチューニングとかDTraceで有名なBrendan GreggさんのLinux BPF Superpowersが面白かったのでBPFとbccに手を出してみました。
BPFはLinuxカーネルのパケットフィルタリングの機能で、その名の通りBerkeley Packet Filterです。bcc「BPF Compiler Collection」というのはBPFを使いやすくするためのツールです。 BPFを使う場合、一番基本的なのはbpf(2)を使ってc言語で書くことだと思います。bccはpython+c言語でBPFを使うようになっています。 (´-`).oO(BPFとbccの関係というのはkprobesとsystemtapの関係に近いような気がします
で、Berkeley Packet Filterでデバッグってなんだよ?ってなると思うのですが、それは正解ですね。ほんと、何でパケットフィルタでメモリリークの検出とかファイルシステムのパフォーマンス見てるのかよくわかりませんw
まあ、細かいことはさておき、使ってみましょう。多分最近のディストリビューションではBPFの機能は有効になっているんじゃないかと思うのですが、なってなかったら自前でビルドですね。ARCH LinuxのカーネルはBFP有効になっています。必要なのはbccのパッケージでこれはAURにあります。あとはlinux-headers、linux-api-headers辺りも必要かもしれません。自分は自前ビルドのカーネルでやりました。
まず、サンプルを動かしてみましょう。githubからコードをクローンして、hello_world.pyを動かします。これはsys_cloneにprobeを登録して、関数が呼ばれるとHello, World!を表示します。prefixとしてkprobe__をつけるのがポイントです。
from bcc import BPF BPF(text='void kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); }').trace_print()
masami@saga:~/codes/bcc/examples (master)$ sudo ./hello_world.py thread.rb:24-1937 [000] d..2 409.429035: : Hello, World! thread.rb:24-9544 [001] d..2 411.371223: : Hello, World! thread.rb:24-9544 [001] d..2 411.371430: : Hello, World!
bpf_trace_printk()を使うとプロセス名(current->comm)、pid(current->pid)なんかは自動で表示してくれます。 text=としているところはc言語のコードです。これは以下のようにファイルから読み込ませることもできます。
BPF(src_file = "foo.c")
とまあ、基本的な使い方を抑えたところで実際に使ってみます。今回はcreate_new_namespaces()関数にprobeを登録してみました。 カーネル内のcreate_new_namespaces()が呼ばれたらプロセス名、pid、cloneのフラグを保存しておいて、それをperfと組み合わせてユーザーランドでデータを表示しています。
#!/usr/bin/env python from bcc import BPF import ctypes as ct prog = """ #include <uapi/linux/ptrace.h> #include <linux/nsproxy.h> struct namespace_creator_t { u64 pid; char comm[256]; u64 flags; }; BPF_PERF_OUTPUT(events); void kprobe__create_new_namespaces(struct pt_regs *ctx, unsigned long flags, struct task_struct *tsk, struct user_namespace *user_ns, struct fs_struct *new_fs) { struct namespace_creator_t nc = {}; u32 pid; nc.flags = flags; pid = bpf_get_current_pid_tgid(); nc.pid = pid; bpf_get_current_comm(&nc.comm, sizeof(nc.comm)); events.perf_submit(ctx, &nc, sizeof(nc)); } """ class NameSpaceCreator(ct.Structure): _fields_ = [ ("pid", ct.c_ulonglong), ("comm", ct.c_char * 256), ("flags", ct.c_ulonglong) ] def flags_string(flags): buf = [] if flags & 0x04000000: buf.append("CLONE_NEWUTS ") if flags & 0x08000000: buf.append("CLONE_NEWIPC ") if flags & 0x10000000: buf.append("CLONE_NEWUSER ") if flags & 0x20000000: buf.append("CLONE_NEWPID ") if flags & 0x40000000: buf.append("CLONE_NEWNET ") s = "" for b in buf: s += b return s.strip() def print_event(cpu, data, size): event = ct.cast(data, ct.POINTER(NameSpaceCreator)).contents print("pid %d(%s) flags: 0x%x(%s)" % (event.pid, event.comm, event.flags, flags_string(event.flags))) print("Ctrl-c to stop") b = BPF(text=prog) b["events"].open_perf_buffer(print_event) while 1: b.kprobe_poll()
実行するとこんな感じになります。
masami@saga:~/codes/ns_bfp$ sudo ./ns_bfp.py Ctrl-c to stop pid 5228(b'unshare') flags: 0x44000000(CLONE_NEWUTS CLONE_NEWNET) pid 1427(b'chrome') flags: 0x20000011(CLONE_NEWPID)
pid5228は以下のようにunshare(1)を実行したときのものです。
masami@saga:~$ sudo unshare -u -n /bin/bash [root@saga masami]#
pid1427はchromeでタブを一つ開いたときのものです。
とまあ、こんな感じで本来はパケットフィルタのBPFを使ってkprobeとperfを組み合わせた形でコードを書くことができます。 bccを使うとpython+c言語でスクリプトを書けるので自由度がかなり高いですね。
Systems Performance: Enterprise and the Cloud
- 作者: Brendan Gregg
- 出版社/メーカー: Prentice Hall
- 発売日: 2013/10/07
- メディア: Kindle版
- この商品を含むブログを見る