rbbcc 0.3.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.semaphore/semaphore.yml +3 -1
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +6 -6
  5. data/README.md +4 -0
  6. data/docs/README.md +2 -0
  7. data/docs/answers/01-hello-world.rb +16 -0
  8. data/docs/answers/02-sys_sync.rb +18 -0
  9. data/docs/answers/03-hello_fields.rb +33 -0
  10. data/docs/answers/04-sync_timing.rb +46 -0
  11. data/docs/answers/05-sync_count.rb +54 -0
  12. data/docs/answers/06-disksnoop.rb +71 -0
  13. data/docs/answers/07-hello_perf_output.rb +59 -0
  14. data/docs/answers/08-sync_perf_output.rb +60 -0
  15. data/docs/answers/09-bitehist.rb +32 -0
  16. data/docs/answers/10-disklatency.rb +51 -0
  17. data/docs/answers/11-vfsreadlat.c +46 -0
  18. data/docs/answers/11-vfsreadlat.rb +66 -0
  19. data/docs/answers/12-urandomread.rb +38 -0
  20. data/docs/answers/13-disksnoop_fixed.rb +108 -0
  21. data/docs/answers/14-strlen_count.rb +46 -0
  22. data/docs/answers/15-nodejs_http_server.rb +44 -0
  23. data/docs/answers/16-task_switch.c +23 -0
  24. data/docs/answers/16-task_switch.rb +17 -0
  25. data/docs/answers/node-server.js +11 -0
  26. data/docs/projects_using_rbbcc.md +43 -0
  27. data/docs/tutorial_bcc_ruby_developer.md +774 -0
  28. data/docs/tutorial_bcc_ruby_developer_japanese.md +770 -0
  29. data/examples/networking/http_filter/http-parse-simple.c +114 -0
  30. data/examples/networking/http_filter/http-parse-simple.rb +85 -0
  31. data/examples/ruby_usdt.rb +105 -0
  32. data/examples/sbrk_trace.rb +204 -0
  33. data/examples/tools/bashreadline.rb +83 -0
  34. data/lib/rbbcc/bcc.rb +73 -20
  35. data/lib/rbbcc/clib.rb +7 -2
  36. data/lib/rbbcc/debug.rb +17 -0
  37. data/lib/rbbcc/table.rb +16 -22
  38. data/lib/rbbcc/usdt.rb +21 -4
  39. data/lib/rbbcc/version.rb +1 -1
  40. data/rbbcc.gemspec +1 -5
  41. metadata +34 -61
@@ -0,0 +1,770 @@
1
+ # RbBCC Ruby Developer Tutorial
2
+
3
+ * オリジナルの Python バージョンは [BCC本家のリポジトリ](https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md) にあります。
4
+ * この Ruby 版チュートリアルは、日本語版も含め BCC のライセンスに従います。
5
+
6
+ ---
7
+
8
+ このチュートリアルは [RbBCC](https://github.com/udzura/rbbcc/) を用いて、Rubyのインタフェースにより bcc のツールを開発するためのチュートリアルです。今回は「可観測性」のパートのみが執筆されています。コードスニペットは [bcc](https://github.com/iovisor/bcc/tree/master/tools) の各所のものを参考にしています: ぜひそれらのライセンスも参照してください。そして、私たちはそれらの Ruby バージョンを [`answers/`](answers/) リポジトリに配置しています。
9
+
10
+ 同時に、 bcc 開発者の [リファレンスガイド](https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#bpf-c) も参照し、 C のインターフェースも理解してください。
11
+
12
+ また、 Python と Lua の bcc インターフェースもあるので、 bcc のオリジナルを参照してください。
13
+
14
+ ## Observability - 可観測性
15
+
16
+ この可観測性のチュートリアルは 17 のレッスンを含んでいます。
17
+
18
+ ### Lesson 1. Hello World
19
+
20
+ [answers/01-hello-world.rb](answers/01-hello-world.rb) を実行しましょう。そして、別のターミナルセッションでいくつかコマンドを(例: `"ls"` )発行しましょう。プロセスを作るたびに「`Hello, World!`」がプリントされるはずです。もし出ない場合、bccのインストールに問題があるでしょう: [BCCの INSTALL.md](https://github.com/iovisor/bcc/blob/master/INSTALL.md) と [rbbcc getting started](getting_started.md) を見てください。
21
+
22
+ ```bash
23
+ ## もし bundler の環境で実行しているのなら、 `bundle exec' をつけてください。
24
+ # ruby answers/01-hello-world.rb
25
+ Found fnc: kprobe__sys_clone
26
+ Attach: p___x64_sys_clone
27
+ <...>-17950 [000] .... 244107.900795: 0: Hello, World!
28
+ bash-17950 [000] .... 244110.775263: 0: Hello, World!
29
+ bash-17950 [000] .... 244114.080360: 0: Hello, World!
30
+ ```
31
+
32
+ 6つの学ぶべきことがあります:
33
+
34
+ 1. ```text: '...'```: これは BPF プログラムをインラインで定義しています。このプログラムは、Cで書きます。
35
+
36
+ 1. ```kprobe__sys_clone()```: これはkprobeによるカーネルの動的トレーシングをするためのショートカット規約です。もし、Cの関数が ``kprobe__`` から開始していたら。残りは計測するカーネルの関数名として扱われます。この場合、 ```sys_clone()``` です。
37
+
38
+ 1. ```void *ctx```: ctx は型があるのですが、今回は使わないので ```void *``` にキャストして捨てています。
39
+
40
+ 1. ```bpf_trace_printk()```: シンプルなカーネルユーティリティで、 ```trace_pipe (/sys/kernel/debug/tracing/trace_pipe)``` に printf() をします。これは単純な例なら問題ないのですが、制限もあります: 引数が3つまで、 `%s` は1つまで、そして `trace_pipe` はマシングローバルであること。なので、並列実行のプログラムではアウトプットがクラッシュするでしょう。より良いインタフェースに `BPF_PERF_OUTPUT()` があり、後述します。
41
+
42
+ 1. ```return 0;```: おまじないです(理由を詳しく知りたければ [bcc#139](https://github.com/iovisor/bcc/issues/139) まで)。
43
+
44
+ 1. ```Table#trace_print```: Ruby側の、trace_pipeを読み込んでアウトプットをプリントするメソッドです。
45
+
46
+ ### Lesson 2. sys_sync()
47
+
48
+ カーネル関数 `sys_sync()` をトレースするプログラムを書きましょう。実行のたびに "sys_sync() called" をプリントします。トレース中別のターミナルで ```sync``` コマンドを打てばテストできます。さきほどの `hello_world.rb` に必要なものが全て書かれています。
49
+
50
+ プログラム起動時に "Tracing sys_sync()... Ctrl-C to end." と出力して、改善しましょう。ヒント: これはただの Ruby プログラムで、Ctrl-Cを押したときに投げられる `Interrupt` 例外を rescue できるはずです。
51
+
52
+ 回答例の一つです: [answers/02-sys_sync.rb](answers/02-sys_sync.rb)
53
+
54
+ Tipsとして、システムコール `sync(2)` を明示的にRubyから呼ぶことも可能です:
55
+
56
+ ```console
57
+ # ausyscall sync
58
+ sync 162
59
+ # ruby -e 'syscall(162)'
60
+ ```
61
+
62
+ ### Lesson 3. hello_fields.rb
63
+
64
+ プログラムは: [answers/03-hello_fields.rb](answers/03-hello_fields.rb). アウトプットのサンプル (コマンドを別のターミナルで打つこと):
65
+
66
+ ```
67
+ # bundle exec ruby ./docs/answers/03-hello_fields.rb
68
+ TIME(s) COMM PID MESSAGE
69
+ 24585001.174885999 sshd 1432 Hello, World!
70
+ 24585001.195710000 sshd 15780 Hello, World!
71
+ 24585001.991976000 systemd-udevd 484 Hello, World!
72
+ 24585002.276147000 bash 15787 Hello, World!
73
+ ```
74
+
75
+ コードは:
76
+
77
+ ```ruby
78
+ require "rbbcc"
79
+ include RbBCC
80
+
81
+ # define BPF program
82
+ prog = <<BPF
83
+ int hello(void *ctx) {
84
+ bpf_trace_printk("Hello, World!\\n");
85
+ return 0;
86
+ }
87
+ BPF
88
+
89
+ # load BPF program
90
+ b = BCC.new(text: prog)
91
+ b.attach_kprobe(event: b.get_syscall_fnname("clone"), fn_name: "hello")
92
+
93
+ # header
94
+ puts("%-18s %-16s %-6s %s" % ["TIME(s)", "COMM", "PID", "MESSAGE"])
95
+
96
+ # format output
97
+ begin
98
+ b.trace_fields do |task, pid, cpu, flags, ts, msg|
99
+ print("%-18.9f %-16s %-6d %s" % [ts, task, pid, msg])
100
+ end
101
+ rescue Interrupt
102
+ puts
103
+ puts "Done"
104
+ rescue => e
105
+ p e
106
+ retry
107
+ end
108
+ ```
109
+
110
+ これは hello_world.rb に近いもので、 sys_clone() 経由で新しいプロセスをトレースします。しかしいくつか新しい要素があります:
111
+
112
+ 1. ```prog:```: 今回私たちは、Cプログラムを変数に格納し、あとで参照しました。これは、 `String#sub!` などで(例えばコマンドラインの入力をもとに)文字列を操作する際に便利でしょう。
113
+
114
+ 1. ```hello()```: いま、私たちはCの関数を ```kprobe__``` ショートカットなしで宣言しました。後ほど説明します。BPFプログラムで宣言されたどのC関数も、probeの際に実行されることを意図されます。そのため ```pt_reg* ctx``` という変数を最初の引数に指定する必要があります。もしprobeでは実行されることのないヘルパー関数を定義する必要があれば、 ```static inline``` を宣言してコンパイラにインライン化をしてもらう必要があるでしょう。場合により ```_always_inline``` という関数attributeを指定する必要もあるでしょう。
115
+
116
+ 1. ```b.attach_kprobe(event: b.get_syscall_fnname("clone"), fn_name: "hello")```: カーネルのcloneシステムコール関数からkprobeをつくり、先ほど定義した hello() を登録、実行させます。 attach_kprobe() を複数回呼び出して、BPF内の関数を複数のカーネル関数とひもづけることも可能です。
117
+
118
+ 1. ```b.trace_fields do |...|```: trace_pipe を一行読みこんだ内容をブロック引数に格納したループを回します(ブロックなしの場合、 readline() のように読み込んだ返り値のセットを返却します)。trace_print() に近いですがこちらの方がアウトプットの加工には便利です。現実的なツールには `BPF_PERF_OUTPUT()` を使いましょう。
119
+
120
+ ### Lesson 4. sync_timing.rb
121
+
122
+ 思い出しましょう、システム管理者が ```reboot``` をする前に、 ```sync``` を3回、遅いコンソールに打ち込んでいた時代を...。それにより最初の非同期なsyncを確実に完了させていたのでしょうかね? しかしそれから、誰かが ```sync;sync;sync``` の方がスマートだと思いついて、一行で全てを実行するプラクティスが普及しました。最初の目的がダメになってしまうにうも関わらず! さらにその後、 sync コマンドは同期的になり、さらに色々な理由でこのプラクティスは馬鹿げたものになりました。やれやれ。
123
+
124
+ この後のサンプルは、どれだけ素早い間隔で ```do_sync``` が呼び出されたかを計測し、もし1秒よりも感覚が小さかったらそれをプリントするものです。```sync;sync;sync``` などと打った場合は2番目と3番目のsyncを表示してくれることでしょう:
125
+
126
+ ```
127
+ # ruby ./answers/04-sync_timing.rb
128
+ Tracing for quick sync's... Ctrl-C to end
129
+ At time 0.00 s: multiple syncs detected, last 95 ms ago
130
+ At time 0.10 s: multiple syncs detected, last 96 ms ago
131
+ ```
132
+
133
+ プログラムは [answers/04-sync_timing.rb](answers/04-sync_timing.rb):
134
+
135
+ ```ruby
136
+ require "rbbcc"
137
+ include RbBCC
138
+
139
+ # load BPF program
140
+ b = BCC.new(text: <<BPF)
141
+ #include <uapi/linux/ptrace.h>
142
+
143
+ BPF_HASH(last);
144
+
145
+ int do_trace(struct pt_regs *ctx) {
146
+ u64 ts, *tsp, delta, key = 0;
147
+
148
+ // attempt to read stored timestamp
149
+ tsp = last.lookup(&key);
150
+ if (tsp != 0) {
151
+ delta = bpf_ktime_get_ns() - *tsp;
152
+ if (delta < 1000000000) {
153
+ // output if time is less than 1 second
154
+ bpf_trace_printk("%d\\n", delta / 1000000);
155
+ }
156
+ last.delete(&key);
157
+ }
158
+
159
+ // update stored timestamp
160
+ ts = bpf_ktime_get_ns();
161
+ last.update(&key, &ts);
162
+ return 0;
163
+ }
164
+ BPF
165
+
166
+ b.attach_kprobe(event: b.get_syscall_fnname("sync"), fn_name: "do_trace")
167
+ puts("Tracing for quick sync's... Ctrl-C to end")
168
+
169
+ # format output
170
+ start = 0
171
+ b.trace_fields do |task, pid, cpu, flags, ts, ms|
172
+ start = ts.to_f if start.zero?
173
+ ts = ts.to_f - start
174
+ puts("At time %.2f s: multiple syncs detected, last %s ms ago" % [ts, ms.chomp])
175
+ end
176
+ ```
177
+
178
+ この回の学びです(全部 C の話です):
179
+
180
+ 1. ```bpf_ktime_get_ns()```: 今のカーネル内時間をナノ秒の解像度で返します。
181
+ 1. ```BPF_HASH(last)```: BPF map を、Hash(連想配列)オブジェクトとして作成します。 ```"last"``` という名前です。今回は追加の引数を指定しませんので、 u64 型のkeyとvalueで定義されます。
182
+ 1. ```key = 0```: このkey/valueストアには一つのペアしか登録しません。なので `0` でハードコーディングします。
183
+ 1. ```last.lookup(&key)```: Hashからkeyを探し、存在したらvalueへのポインタを、なければ `NULL` を返します。keyもポインタとして渡してください。
184
+ 1. ```last.delete(&key)```: Hashから指定したkeyを削除します。現在は [カーネルの `.update()` のバグがあるので](https://git.kernel.org/cgit/linux/kernel/git/davem/net.git/commit/?id=a6ed3ea65d9868fdf9eff84e6fe4f666b8d14b02) 、念のため必要です。
185
+ 1. ```last.update(&key, &ts)```: keyと2番目の引数のvalueを関連づけ、それまでのvalueを上書きします。このレコードはタイムスタンプですね。
186
+
187
+ *Note for RbBCC developers:* `trace_fields` メソッドの返り値がPython版と微妙に違うので直した方がいいです。
188
+
189
+ ### Lesson 5. sync_count.rb
190
+
191
+ 先ほどのレッスンの sync_timing.rb を変更し、すべての sync システムコールの呼び出し回数を保存するようにしましょう(早い遅いに関わらず)。そして出力しましょう。このカウントアップはBPFプログラムの中で、いまあるHashに新しいキーを導入することで記録できるでしょう。
192
+
193
+ 回答例の一つは [answers/05-sync_count.rb](answers/05-sync_count.rb) です。
194
+
195
+ ### Lesson 6. disksnoop.rb
196
+
197
+ [answers/06-disksnoop.rb](answers/06-disksnoop.rb) を見てみましょう。これがサンプル出力です:
198
+
199
+ ```
200
+ # bundle exec answers/06-disksnoop.rb
201
+ TIME(s) T BYTES LAT(ms)
202
+ 16458043.436012 W 4096 3.13
203
+ 16458043.437326 W 4096 4.44
204
+ 16458044.126545 R 4096 42.82
205
+ 16458044.129872 R 4096 3.24
206
+ [...]
207
+ ```
208
+
209
+ コードスニペットです:
210
+
211
+ ```ruby
212
+ require 'rbbcc'
213
+ include RbBCC
214
+
215
+ REQ_WRITE = 1 # from include/linux/blk_types.h
216
+
217
+ # load BPF program
218
+ b = BCC.new(text: <<CLANG)
219
+ #include <uapi/linux/ptrace.h>
220
+ #include <linux/blkdev.h>
221
+
222
+ BPF_HASH(start, struct request *);
223
+
224
+ void trace_start(struct pt_regs *ctx, struct request *req) {
225
+ // stash start timestamp by request ptr
226
+ u64 ts = bpf_ktime_get_ns();
227
+
228
+ start.update(&req, &ts);
229
+ }
230
+
231
+ void trace_completion(struct pt_regs *ctx, struct request *req) {
232
+ u64 *tsp, delta;
233
+
234
+ tsp = start.lookup(&req);
235
+ if (tsp != 0) {
236
+ delta = bpf_ktime_get_ns() - *tsp;
237
+ bpf_trace_printk("%d %x %d\\n", req->__data_len,
238
+ req->cmd_flags, delta / 1000);
239
+ start.delete(&req);
240
+ }
241
+ }
242
+ CLANG
243
+
244
+ b.attach_kprobe(event: "blk_start_request", fn_name: "trace_start")
245
+ b.attach_kprobe(event: "blk_mq_start_request", fn_name: "trace_start")
246
+ b.attach_kprobe(event: "blk_account_io_completion", fn_name: "trace_completion")
247
+ [...]
248
+ ```
249
+
250
+ 今回の学習内容です:
251
+
252
+ 1. ```REQ_WRITE```: カーネル関数をRubyのプログラムの中で定義し、後ろで利用しています。BPFのプログラムの中で REQ_WRITE 定数を使う場合、 `#include` で適切なヘッダを読み込むことで、自分で定義しなくても動作するでしょう。
253
+ 1. ```trace_start(struct pt_regs *ctx, struct request *req)```: この関数はあとでkprobeにアタッチします。このkprobe用の関数の最初の引数は ```struct pt_regs *ctx``` で、BPFのコンテクストを表します。第2引数以降で実際のカーネル関数の引数を列挙します。今回はこれを blk_start_request() に割り当てる予定で、これの最初の引数の型は ```struct request *``` です。
254
+ 1. ```start.update(&req, &ts)```: ```struct request``` へのポインタをHashのkeyに使っています。どういうことか? トレーシングでよく使う技です。構造体のポインタはkeyとしてふさわしいもので、なぜならその値はユニークだからです: 2つの構造体は同じポインタアドレスを持たないため。(freeされてアドレスが再利用される場合にだけは注意しましょう。)なので、私たちがここでしたいのは単にリクエストにタグを打ちたいだけで、それぞれのリクエストはdisk I/Oの詳細を記述しており、それごとにタイムスタンプを発行することで間隔を計測します。ちなみにタイムスタンプを格納する上では2つのkeyが使えます: 構造体のポインタと、Thread ID(特に、関数の開始とreturnまでを計測する場合)です。
255
+ 1. ```req->__data_len```: ```struct request```のメンバをデリファレンスしています。カーネルのソースコードを見てメンバが何か確認しましょう。 bcc は実際にはこれらの表現は ```bpf_probe_read()``` の呼び出しに置換しています。時として、複雑なデリファレンスには対応できないので、 ```bpf_probe_read()``` を直接呼び必要があるでしょう。
256
+
257
+ これは大変面白いプログラムで、このコードの理解ができたのなら、多くの重要な基本を理解したと言えるでしょう。なお、いまだに bpf_trace_printk() を利用していますので、次でそれを修正しましょう。
258
+
259
+ ### Lesson 7. hello_perf_output.rb
260
+
261
+ いよいよ、 `bpf_trace_printk()` の利用をやめ、適切な `BPF_PERF_OUTPUT()` インタフェースを使うようにしましょう。これは、 `trace_field()` がデフォルトで付与してくれるPIDやタイムスタンプなどの情報を自分で直接取得することを意味します。これが別のターミナルでコマンドを実行しながらのサンプルのアウトプットです。
262
+
263
+ ```
264
+ # bundle exec answers/07-hello_perf_output.rb
265
+ TIME(s) COMM PID MESSAGE
266
+ 0.000000000 bash 22986 Hello, perf_output!
267
+ 0.021080275 systemd-udevd 484 Hello, perf_output!
268
+ 0.021359520 systemd-udevd 484 Hello, perf_output!
269
+ 0.021590610 systemd-udevd 484 Hello, perf_output!
270
+ [...]
271
+ ```
272
+
273
+ コードは [answers/07-hello_perf_output.rb](answers/07-hello_perf_output.rb) です:
274
+
275
+ ```ruby
276
+ #!/usr/bin/env ruby
277
+ #
278
+ # This is a Hello World example that uses BPF_PERF_OUTPUT.
279
+ # Ported from hello_perf_output.py
280
+
281
+ require 'rbbcc'
282
+ include RbBCC
283
+
284
+ # define BPF program
285
+ prog = """
286
+ #include <linux/sched.h>
287
+
288
+ // define output data structure in C
289
+ struct data_t {
290
+ u32 pid;
291
+ u64 ts;
292
+ char comm[TASK_COMM_LEN];
293
+ };
294
+ BPF_PERF_OUTPUT(events);
295
+
296
+ int hello(struct pt_regs *ctx) {
297
+ struct data_t data = {};
298
+
299
+ data.pid = bpf_get_current_pid_tgid();
300
+ data.ts = bpf_ktime_get_ns();
301
+ bpf_get_current_comm(&data.comm, sizeof(data.comm));
302
+
303
+ events.perf_submit(ctx, &data, sizeof(data));
304
+
305
+ return 0;
306
+ }
307
+ """
308
+
309
+ # load BPF program
310
+ b = BCC.new(text: prog)
311
+ b.attach_kprobe(event: b.get_syscall_fnname("clone"), fn_name: "hello")
312
+
313
+ # header
314
+ puts("%-18s %-16s %-6s %s" % ["TIME(s)", "COMM", "PID", "MESSAGE"])
315
+
316
+ # process event
317
+ start = 0
318
+ print_event = lambda { |cpu, data, size|
319
+ event = b["events"].event(data)
320
+ if start == 0
321
+ start = event.ts
322
+ end
323
+
324
+ time_s = ((event.ts - start).to_f) / 1000000000
325
+ puts("%-18.9f %-16s %-6d %s" % [time_s, event.comm, event.pid,
326
+ "Hello, perf_output!"])
327
+ }
328
+
329
+ # loop with callback to print_event
330
+ b["events"].open_perf_buffer(&print_event)
331
+
332
+ loop do
333
+ b.perf_buffer_poll()
334
+ end
335
+ ```
336
+
337
+ 学ぶべきこと:
338
+
339
+ 1. ```struct data_t```: これは、カーネルからユーザースペースに渡すデータの構造を宣言しています。
340
+ 1. ```BPF_PERF_OUTPUT(events)```: 私たちが今から使うチャンネルを "events" と名付けています。
341
+ 1. ```struct data_t data = {};```: 空の `data_t` 構造体を作成し、その後中身を埋めます。
342
+ 1. ```bpf_get_current_pid_tgid()```: 下位の32bitで、「プロセスID/PID」を返します(カーネルから見たPIDです。ユーザスペースからは、一般にスレッドIDと呼ばれます)。そしてスレッドグループID/TGIDは上位32bitに含まれています(これは、ユーザスペースで言うところのPIDです)。この関数を返り値をu32型の変数に格納すると、上位の32bit分は破棄されます。PIDとTGIDのどちらを利用すべきでしょうか? マルチスレッドなアプリケーションでは、どのスレッドもTGIDは同じはずです。したがってもし必要であればPIDで区別する必要があります。そしてこれはツールのエンドユーザーが予期しているところでもあるでしょう。
343
+ 1. ```bpf_get_current_comm()```: 最初の引数のポインタアドレスに現在のプロセス名を格納します。
344
+ 1. ```events.perf_submit()```: ここで、perfのリングバッファを経由して、イベントをユーザスペースに送信します。
345
+ 1. ```print_event = lambda { ... }```: ```events``` ストリームから流れてくるイベントをハンドルするRubyのprocオブジェクト(lamnbda)を定義します; ところでPythonと違い、Ruby版の `Table#open_perf_buffer` は直接ブロックを受け取ることもできます :)
346
+ 1. ```b["events"].event(data)```: ここで、Cの定義から自動生成したRubyのオブジェクトとして、イベントデータを受け取ります。
347
+ 1. ```b["events"].open_perf_buffer(&print_event)```: proc ```print_event``` をCで定義した ```events``` と関連づけます。
348
+ 1. ```loop { b.perf_buffer_poll() }```: イベントが来るのを待ち構えます。
349
+
350
+ ### Lesson 8. sync_perf_output.rb
351
+
352
+ 前のレッスンのsync_timing.rbを、 ```BPF_PERF_OUTPUT``` を使うよう書き換えてください。
353
+
354
+ 回答例はこちら: [answers/08-sync_perf_output.rb](answers/08-sync_perf_output.rb).
355
+
356
+ ### Lesson 9. bitehist.rb
357
+
358
+ 次のツールは、disk I/Oのサイズを記録しヒストグラムで可視化します。サンプル出力です:
359
+
360
+ ```
361
+ # bundle exec answers/09-bitehist.rb
362
+ Tracing... Hit Ctrl-C to end.
363
+ ^C
364
+ kbytes : count distribution
365
+ 0 -> 1 : 3 | |
366
+ 2 -> 3 : 0 | |
367
+ 4 -> 7 : 211 |********** |
368
+ 8 -> 15 : 0 | |
369
+ 16 -> 31 : 0 | |
370
+ 32 -> 63 : 0 | |
371
+ 64 -> 127 : 1 | |
372
+ 128 -> 255 : 800 |**************************************|
373
+ ```
374
+
375
+ コードは [answers/09-bitehist.rb](answers/09-bitehist.rb) です:
376
+
377
+ ```ruby
378
+ require 'rbbcc'
379
+ include RbBCC
380
+
381
+ # load BPF program
382
+ b = BCC.new(text: <<BPF)
383
+ #include <uapi/linux/ptrace.h>
384
+ #include <linux/blkdev.h>
385
+
386
+ BPF_HISTOGRAM(dist);
387
+
388
+ int kprobe__blk_account_io_completion(struct pt_regs *ctx, struct request *req)
389
+ {
390
+ dist.increment(bpf_log2l(req->__data_len / 1024));
391
+ return 0;
392
+ }
393
+ BPF
394
+
395
+ # header
396
+ puts("Tracing... Hit Ctrl-C to end.")
397
+
398
+ # trace until Ctrl-C
399
+ begin
400
+ loop { sleep 0.1 }
401
+ rescue Interrupt
402
+ puts
403
+ end
404
+
405
+ # output
406
+ b["dist"].print_log2_hist("kbytes")
407
+ ```
408
+
409
+ ここまでのレッスンのおさらい:
410
+
411
+ - ```kprobe__```: このプレフィックスにより、その後ろの名前のカーネル関数をkprobeでの計測対象にすることを意味します。
412
+ - ```struct pt_regs *ctx, struct request *req```: kprobeへの引数です。 ```ctx``` でBPFのコンテクストを取得し、 ```req``` は計測対象の関数 ```blk_account_io_completion()``` の最初の引数です。
413
+ - ```req->__data_len```: その `req` のメンバをデリファレンスします。
414
+
415
+ 新しい要素です:
416
+
417
+ 1. ```BPF_HISTOGRAM(dist)```: ヒストグラムのためのBPF mapオブジェクトを定義し、 "dist" と名付けます。
418
+ 1. ```dist.increment()```: 第1引数で指定されたインデックスのヒストグラム上の階級を1つインクリメントします。オプションとして。第2引数にインクリメントする度合いを指定することもできます。
419
+ 1. ```bpf_log2l()```: 引数の値の log2() を計算し返します。これがヒストグラムのインデックスになるので、2のべき乗のヒストグラムを作成することになります。
420
+ 1. ```b["dist"].print_log2_hist("kbytes")```: "dist" に加工のしたヒストグラムを、 "kbytes" と言うヘッダで出力します。カーネルからユーザスペースへ送信されるデータは各階級のカウントだけになるので、効率的です。
421
+
422
+ ### Lesson 10. disklatency.rb
423
+
424
+ disk I/O のレイテンシを計測し、ヒストグラムを出力するプログラムを書きましょう。disk I/Oのトレースと時間計測は以前書いたdisksnoop.rbで、ヒストグラムの作り方はさきほどのbitehist.rbを参考にできます。
425
+
426
+ 回答例は: [answers/10-disklatency.rb](answers/10-disklatency.rb).
427
+
428
+ ### Lesson 11. vfsreadlat.rb
429
+
430
+ このサンプルはRubyとCのファイルに分かれています。出力例です:
431
+
432
+ ```
433
+ # bundle exec answers/11-vfsreadlat.rb 1
434
+ Tracing... Hit Ctrl-C to end.
435
+ usecs : count distribution
436
+ 0 -> 1 : 0 | |
437
+ 2 -> 3 : 2 |*********** |
438
+ 4 -> 7 : 7 |****************************************|
439
+ 8 -> 15 : 4 |********************** |
440
+
441
+ usecs : count distribution
442
+ 0 -> 1 : 29 |****************************************|
443
+ 2 -> 3 : 28 |************************************** |
444
+ 4 -> 7 : 4 |***** |
445
+ 8 -> 15 : 8 |*********** |
446
+ 16 -> 31 : 0 | |
447
+ 32 -> 63 : 0 | |
448
+ 64 -> 127 : 0 | |
449
+ 128 -> 255 : 0 | |
450
+ 256 -> 511 : 2 |** |
451
+ 512 -> 1023 : 0 | |
452
+ 1024 -> 2047 : 0 | |
453
+ 2048 -> 4095 : 0 | |
454
+ 4096 -> 8191 : 4 |***** |
455
+ 8192 -> 16383 : 6 |******** |
456
+ 16384 -> 32767 : 9 |************ |
457
+ 32768 -> 65535 : 6 |******** |
458
+ 65536 -> 131071 : 2 |** |
459
+
460
+ usecs : count distribution
461
+ 0 -> 1 : 11 |****************************************|
462
+ 2 -> 3 : 2 |******* |
463
+ 4 -> 7 : 10 |************************************ |
464
+ 8 -> 15 : 8 |***************************** |
465
+ 16 -> 31 : 1 |*** |
466
+ 32 -> 63 : 2 |******* |
467
+ [...]
468
+ ```
469
+
470
+ コードは [answers/11-vfsreadlat.rb](answers/11-vfsreadlat.rb) と [answers/11-vfsreadlat.c](answers/11-vfsreadlat.c) にあるので見てみましょう。学べることは:
471
+
472
+ 1. ```b = BCC.new(src_file: "vfsreadlat.c")```: BPF Cプログラムを別の場所から読み込みます。Rubyファイルと同じディレクトリにあればOKです。
473
+ 1. ```b.attach_kretprobe(event: "vfs_read", fn_name: "do_return")```: BPFのC関数 ```do_return()``` をカーネル関数 ```vfs_read()``` にアタッチします。これは kretprobe と呼ばれるもので、いままでのように関数のエントリーではなく、関数のreturnを計測するものです。
474
+ 1. ```b["dist"].clear()```: ヒストグラムをRuby側からクリアします。定期的なインターバルで計測するためです。
475
+
476
+ ### Lesson 12. urandomread.rb
477
+
478
+ ```dd if=/dev/urandom of=/dev/null bs=8k count=5``` の実行をトレースします:
479
+
480
+ ```
481
+ # bundle exec answers/12-urandomread.rb
482
+ TIME(s) COMM PID GOTBITS
483
+ 24652832.956994001 smtp 24690 384
484
+ 24652837.726500999 dd 24692 65536
485
+ 24652837.727111001 dd 24692 65536
486
+ 24652837.727703001 dd 24692 65536
487
+ 24652837.728294998 dd 24692 65536
488
+ 24652837.728888001 dd 24692 65536
489
+ ```
490
+
491
+ おや!偶然smtpのイベントも捕まえました。コードは [answers/12-urandomread.rb](answers/12-urandomread.rb):
492
+
493
+ ```ruby
494
+ require 'rbbcc'
495
+ include RbBCC
496
+
497
+ b = BCC.new(text: <<BPF)
498
+ TRACEPOINT_PROBE(random, urandom_read) {
499
+ // args is from /sys/kernel/debug/tracing/events/random/urandom_read/format
500
+ bpf_trace_printk("%d\\n", args->got_bits);
501
+ return 0;
502
+ }
503
+ BPF
504
+
505
+ # header
506
+ puts("%-18s %-16s %-6s %s" % ["TIME(s)", "COMM", "PID", "GOTBITS"])
507
+
508
+ # format output
509
+ loop do
510
+ begin
511
+ b.trace_fields do |task, pid, cpu, flags, ts, msg|
512
+ puts("%-18.9f %-16s %-6d %s" % [ts, task, pid, msg])
513
+ end
514
+ rescue Interrupt
515
+ exit
516
+ end
517
+ end
518
+ ```
519
+
520
+ 学ぶことです:
521
+
522
+ 1. ```TRACEPOINT_PROBE(random, urandom_read)```: カーネルの tracepoint(Rubyの `TracePoint` クラスとは名前が同じだけです)である ```random:urandom_read``` を計測します。このtracepointは「安定した(stable)」APIを提供します。カーネル関数のようにバージョンによって変わることがないと言う意味です。そのため、可能な限りkprobeよりもこちらを使うことが推奨されます。 ```perf list``` コマンドを実行すればtracepointのリストが手に入ります。BPFのプログラムをtracepointにアタッチするにはLinuxのバージョン4.7以上が必要です。
523
+ 1. ```args->got_bits```: ```args``` はTRACEPOINT_PROBEマクロが自動定義する変数で、tracepointの引数が格納された構造体です。上にあるコメントはその構造を確認できるLinux上のファイルです。例えば:
524
+
525
+ ```
526
+ # cat /sys/kernel/debug/tracing/events/random/urandom_read/format
527
+ name: urandom_read
528
+ ID: 972
529
+ format:
530
+ field:unsigned short common_type; offset:0; size:2; signed:0;
531
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
532
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
533
+ field:int common_pid; offset:4; size:4; signed:1;
534
+
535
+ field:int got_bits; offset:8; size:4; signed:1;
536
+ field:int pool_left; offset:12; size:4; signed:1;
537
+ field:int input_left; offset:16; size:4; signed:1;
538
+
539
+ print fmt: "got_bits %d nonblocking_pool_entropy_left %d input_entropy_left %d", REC->got_bits, REC->pool_left, REC->input_left
540
+ ```
541
+
542
+ 今回は、 ```got_bits``` メンバを表示しています。
543
+
544
+ ### Lesson 13. disksnoop.rb fixed
545
+
546
+ ここまでのレッスンで作成した disksnoop.rb を、 ```block:block_rq_issue``` と ```block:block_rq_complete``` tracepointを使って書き直しましょう。
547
+
548
+ たとえば、回答例は [answers/13-disksnoop_fixed.rb](answers/13-disksnoop_fixed.rb) です。
549
+
550
+
551
+ ### Lesson 14. strlen_count.rb
552
+
553
+ このプログラムは(カーネルではなく)ユーザレベルの関数をトレースします。今回はライブラリ関数 ```strlen()``` で、その引数の文字列の登場回数をカウントします。こう言う出力です:
554
+
555
+ ```
556
+ # bundle exec answers/14-strlen_count.rb
557
+ Tracing strlen()... Hit Ctrl-C to end.
558
+ ^C COUNT STRING
559
+ 1 " "
560
+ 1 "/bin/ls"
561
+ 1 "."
562
+ 1 "cpudist.py.1"
563
+ 1 ".bashrc"
564
+ 1 "ls --color=auto"
565
+ 1 "key_t"
566
+ [...]
567
+ 10 "a7:~# "
568
+ 10 "/root"
569
+ 12 "LC_ALL"
570
+ 12 "en_US.UTF-8"
571
+ 13 "en_US.UTF-8"
572
+ 20 "~"
573
+ 70 "#%^,~:-=?+/}"
574
+ 340 "\x01\x1b]0;root@bgregg-test: ~\x07\x02root@bgregg-test:~# "
575
+ ```
576
+
577
+ このライブラリ関数で処理される文字列には様々な種類があることが、登場回数のカウントからわかります。たとえば "LC_ALL" と言う文字列は12回 ```strlen()``` の引数になりました。
578
+
579
+ コードは [answers/14-strlen_count.rb](answers/14-strlen_count.rb):
580
+
581
+ ```ruby
582
+ require 'rbbcc'
583
+ include RbBCC
584
+
585
+ # load BPF program
586
+ b = BCC.new(text: <<BPF)
587
+ #include <uapi/linux/ptrace.h>
588
+
589
+ struct key_t {
590
+ char c[80];
591
+ };
592
+ BPF_HASH(counts, struct key_t);
593
+
594
+ int count(struct pt_regs *ctx) {
595
+ if (!PT_REGS_PARM1(ctx))
596
+ return 0;
597
+
598
+ struct key_t key = {};
599
+ u64 zero = 0, *val;
600
+
601
+ bpf_probe_read(&key.c, sizeof(key.c), (void *)PT_REGS_PARM1(ctx));
602
+ // could also use `counts.increment(key)`
603
+ val = counts.lookup_or_try_init(&key, &zero);
604
+ if (val) {
605
+ (*val)++;
606
+ }
607
+ return 0;
608
+ };
609
+ BPF
610
+ b.attach_uprobe(name: "c", sym: "strlen", fn_name: "count")
611
+
612
+ # header
613
+ print("Tracing strlen()... Hit Ctrl-C to end.")
614
+
615
+ # sleep until Ctrl-C
616
+ begin
617
+ sleep(99999999)
618
+ rescue Interrupt
619
+ puts
620
+ end
621
+
622
+ # print output
623
+ puts("%10s %s" % ["COUNT", "STRING"])
624
+ counts = b.get_table("counts")
625
+ counts.items.sort_by{|k, v| v.to_bcc_value }.each do |k, v|
626
+ # unpack following definition of struct key_t above
627
+ puts("%10d %s" % [v.to_bcc_value, k[0, k.size].unpack("Z*")[0]])
628
+ end
629
+ ```
630
+
631
+ 今回の学びです:
632
+
633
+ 1. ```PT_REGS_PARM1(ctx)```: ```strlen()``` の最初の引数をレジスタから取り出します。文字列です。
634
+ 1. ```b.attach_uprobe(name: "c", sym: "strlen", fn_name: "count")```: "c" ライブラリ(もし関数がプログラムの側にあるのなら、バイナリのpathnameを用いてください)にアタッチし、ユーザレベル関数 ```strlen()``` を計測、その実行のたびに私たちが定義したC関数 ```count()``` を呼び出します。
635
+ 1. ```BPF_HASH``` から `Table#items` で取り出した値をRubyで扱うには、面倒ですが ```k/v.to_bcc_value``` をRubyのブロックの中などで呼び出す必要があります。この挙動はRuby版の実装の都合によるものですが、将来変更する可能性があります。
636
+
637
+ ### Lesson 15. nodejs_http_server.rb
638
+
639
+ このプログラムはユーザが静的に定義したトレースポイント(User Statically-Defined Tracing: **USDT**)をprobeとして計測するもので、USDTはユーザランドにおけるカーネルのtracepointに相当するものです。出力例です:
640
+
641
+ ```
642
+ # bundle exec answers/15-nodejs_http_server.rb
643
+ TIME(s) COMM PID ARGS
644
+ 24653324.561322998 node 24728 path:/index.html
645
+ 24653335.343401998 node 24728 path:/images/welcome.png
646
+ 24653340.510164998 node 24728 path:/images/favicon.png
647
+ ```
648
+
649
+ 回答例は [answers/15-nodejs_http_server.rb](answers/15-nodejs_http_server.rb) にあります; 同じディレクトリにあるnode.jsのサーバプログラムを、USDTを有効にした(`--enable-dtrace`)ビルドのnode.jsバイナリで実行して計測する必要があります。
650
+
651
+ ```ruby
652
+ require 'rbbcc'
653
+ include RbBCC
654
+
655
+ if ARGV.size != 1 :
656
+ print("USAGE: #{$0} PID")
657
+ exit()
658
+ end
659
+ pid = ARGV[0]
660
+ debug = !!ENV['DEBUG']
661
+
662
+ # load BPF program
663
+ bpf_text = <<BPF
664
+ #include <uapi/linux/ptrace.h>
665
+ int do_trace(struct pt_regs *ctx) {
666
+ uint64_t addr;
667
+ char path[128]={0};
668
+ bpf_usdt_readarg(6, ctx, &addr);
669
+ bpf_probe_read(&path, sizeof(path), (void *)addr);
670
+ bpf_trace_printk("path:%s\\n", path);
671
+ return 0;
672
+ };
673
+ BPF
674
+
675
+ # enable USDT probe from given PID
676
+ u = USDT.new(pid: pid.to_i)
677
+ u.enable_probe(probe: "http__server__request", fn_name: "do_trace")
678
+ if debug
679
+ puts(u.get_text)
680
+ puts(bpf_text)
681
+ end
682
+
683
+ # initialize BPF
684
+ b = BCC.new(text: bpf_text, usdt_contexts: [u])
685
+ ```
686
+
687
+ 今回の学び:
688
+
689
+ 1. ```bpf_usdt_readarg(6, ctx, &addr)```: USDT probeの6番目の引数が格納されたアドレスを、 ```addr``` に読み込みます。
690
+ 1. ```bpf_probe_read(&path, sizeof(path), (void *)addr)```: ここで、 ```addr``` が示している文字列をBPFプログラム側の ```path``` に格納します。
691
+ 1. ```u = USDT.new(pid: pid.to_i)```: USDTのトレースを与えられたPIDに対してできるよう初期化します。
692
+ 1. ```u.enable_probe(probe: "http__server__request", fn_name: "do_trace")```: 私たちの書いたBPF C関数 ```do_trace()``` をNode.jsのUSDTである ```http__server__request``` probeにアタッチします。
693
+ 1. ```b = BCC.new(text: bpf_text, usdt_contexts: [u])```: BPFのオブジェクトを作る際に、先ほどのUSDTオブジェクト ```u``` を渡す必要があります。
694
+
695
+ もちろん、Ruby自身にもUSDTが存在するので、このレッスンでサンプルを追加する予定です(P/Rは歓迎です)。
696
+
697
+ ### Lesson 16. task_switch.c
698
+
699
+ *これは古いチュートリアルで、ボーナスレッスンとして含めています。ここまでに学んだことの復習と強化のために活用してください。*
700
+
701
+ これはHello Worldよりはもう若干複雑なトレースで、
702
+ このプログラムは実行タスクのスイッチがカーネルで行われるごとに実行されます。
703
+ そして古いPIDと新しいPIDがBPF mapに記録されます。
704
+
705
+ このCプログラムは新しいコンセプト: `prev` 引数を導入しています。
706
+ この引数はBCCのフロントエンドから特別な扱いを受けます。この引数へのアクセスは。kprobeに渡されるセーブ済みのコンテクストから読み込まれます。
707
+ 関数のプロトタイプの2つ目の引数は、kprobeされているカーネル関数の最初の引数と一致する必要があります。
708
+ そのように宣言されれば、プログラムはシームレスにカーネル関数のパラメータにアクセス可能です。
709
+
710
+ ```c
711
+ #include <uapi/linux/ptrace.h>
712
+ #include <linux/sched.h>
713
+
714
+ struct key_t {
715
+ u32 prev_pid;
716
+ u32 curr_pid;
717
+ };
718
+
719
+ BPF_HASH(stats, struct key_t, u64, 1024);
720
+ int count_sched(struct pt_regs *ctx, struct task_struct *prev) {
721
+ struct key_t key = {};
722
+ u64 zero = 0, *val;
723
+
724
+ key.curr_pid = bpf_get_current_pid_tgid();
725
+ key.prev_pid = prev->pid;
726
+
727
+ // could also use `stats.increment(key);`
728
+ val = stats.lookup_or_try_init(&key, &zero);
729
+ if (val) {
730
+ (*val)++;
731
+ }
732
+ return 0;
733
+ }
734
+ ```
735
+
736
+ ユーザスペース側のコンポネントはCファイルを以下のように読み込み、
737
+ `finish_task_switch` カーネル関数にアタッチします。
738
+ BPFオブジェクトは `BCC#[]` を経由して、プログラムの BPF_HASH にそれぞれの名前でアクセスできます。
739
+ これにより、カーネル側に存在する値にパススルーでアクセスが可能になります。
740
+ 取り出したオブジェクトはRubyのHashのように扱えます: each を実装した `Enumerable` で、また読み込み、更新、削除が全て可能です。
741
+
742
+ ```ruby
743
+ require 'rbbcc'
744
+ include RbBCC
745
+
746
+ b = BCC.new(src_file: "16-task_switch.c")
747
+ b.attach_kprobe(event: "finish_task_switch", fn_name: "count_sched")
748
+
749
+ # generate many schedule events
750
+ 100.times { sleep 0.01 }
751
+
752
+ b["stats"].each do |_k, v|
753
+ k = _k[0, 8].unpack("i! i!") # Handling pointer without type!!
754
+ puts("task_switch[%5d->%5d]=%u" % [k[0], k[1], v.to_bcc_value])
755
+ end
756
+ ```
757
+
758
+ これらのプログラムは2つのファイル [answers/16-task_switch.c](answers/16-task_switch.c) と [answers/16-task_switch.rb](answers/16-task_switch.rb) で確認できます。
759
+
760
+ ### Lesson 17. Further Study
761
+
762
+ さらに学習したい場合、 [BCC のオリジナルのドキュメント](https://github.com/iovisor/bcc/tree/master/docs) や、 Sasha Goldshteinの[linux-tracing-workshop](https://github.com/goldshtn/linux-tracing-workshop) に発展的な内容が含まれています。また、 rbbcc/bcc の /tools ディレクトリも参考になるでしょう。今のところは、Pythonのコードの知識も助けになることでしょう :)
763
+
764
+ rbbccにツールを作って貢献したい場合、 [CONTRIBUTING.md](../CONTRIBUTING.md) を読んでください(準備中です)。 [README.md](../README.md) の下にはコンタクトすべきリストもあります(これも準備中; いまいまは @udzura で構いません)。では、良い旅を。Enjoy Tracing!
765
+
766
+ ---
767
+
768
+ ## Networking
769
+
770
+ やる気はある。