rbbcc 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,772 @@
1
+ # RbBCC Ruby Developer Tutorial
2
+
3
+ * Original Python version is [at BCC's repo](https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md)
4
+ * This Ruby version of tutorial follows the license of BCC.
5
+
6
+ ---
7
+
8
+ This tutorial is about developing bcc tools and programs using the Ruby interface, using [RbBCC](https://github.com/udzura/rbbcc/). In this time the oart of observability is implemented. Snippets are taken from various programs in [bcc](https://github.com/iovisor/bcc/tree/master/tools): see their files for licences. And we have implemented their Ruby versions and put them on [`answers/`](answers/).
9
+
10
+ Also see the bcc developer's [reference_guide.md](https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#bpf-c) for C interface.
11
+
12
+ There is also Python and lua interface for bcc. See original.
13
+
14
+ ## Observability
15
+
16
+ This observability tutorial contains 17 lessons, and XX enumerated things to learn.
17
+
18
+ ### Lesson 1. Hello World
19
+
20
+ Start by running [answers/01-hello-world.rb](answers/01-hello-world.rb), while running some commands (eg, "ls") in another session. It should print "Hello, World!" for new processes. If not, start by fixing bcc: see [BCC's INSTALL.md](https://github.com/iovisor/bcc/blob/master/INSTALL.md) and [rbbcc getting started](getting_started.md).
21
+
22
+ ```bash
23
+ ## If you're running rbbcc in bundled environment, follow this command after `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
+ There are six things to learn from this:
33
+
34
+ 1. ```text: '...'```: This defines a BPF program inline. The program is written in C.
35
+
36
+ 1. ```kprobe__sys_clone()```: This is a short-cut for kernel dynamic tracing via kprobes. If the C function begins with ``kprobe__``, the rest is treated as a kernel function name to instrument, in this case, ```sys_clone()```.
37
+
38
+ 1. ```void *ctx```: ctx has arguments, but since we aren't using them here, we'll just cast it to ```void *```.
39
+
40
+ 1. ```bpf_trace_printk()```: A simple kernel facility for printf() to the common trace_pipe (/sys/kernel/debug/tracing/trace_pipe). This is ok for some quick examples, but has limitations: 3 args max, 1 %s only, and trace_pipe is globally shared, so concurrent programs will have clashing output. A better interface is via BPF_PERF_OUTPUT(), covered later.
41
+
42
+ 1. ```return 0;```: Necessary formality (if you want to know why, see [bcc#139](https://github.com/iovisor/bcc/issues/139)).
43
+
44
+ 1. ```Table#trace_print```: A bcc routine that reads trace_pipe and prints the output.
45
+
46
+ ### Lesson 2. sys_sync()
47
+
48
+ Write a program that traces the sys_sync() kernel function. Print "sys_sync() called" when it runs. Test by running ```sync``` in another session while tracing. The hello_world.rb program has everything you need for this.
49
+
50
+ Improve it by printing "Tracing sys_sync()... Ctrl-C to end." when the program first starts. Hint: it's just Ruby and you can rescue `Interrupt` exception.
51
+
52
+ On of the answer example is: [answers/02-sys_sync.rb](answers/02-sys_sync.rb)
53
+
54
+ Tips: how to call `sync(2)` explicitly via Ruby; or just type `sync` at your terminal:
55
+
56
+ ```console
57
+ # ausyscall sync
58
+ sync 162
59
+ # ruby -e 'syscall(162)'
60
+ ```
61
+
62
+ ### Lesson 3. hello_fields.rb
63
+
64
+ This program is in [answers/03-hello_fields.rb](answers/03-hello_fields.rb). Sample output (run commands in another session):
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
+ Code:
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
+ This is similar to hello_world.rb, and traces new processes via sys_clone() again, but has a few more things to learn:
111
+
112
+ 1. ```prog:```: This time we declare the C program as a variable, and later refer to it. This is useful if you want to add some string substitutions based on command line arguments.
113
+
114
+ 1. ```hello()```: Now we're just declaring a C function, instead of the ```kprobe__``` shortcut. We'll refer to this later. All C functions declared in the BPF program are expected to be executed on a probe, hence they all need to take a ```pt_reg* ctx``` as first argument. If you need to define some helper function that will not be executed on a probe, they need to be defined as ```static inline``` in order to be inlined by the compiler. Sometimes you would also need to add ```_always_inline``` function attribute to it.
115
+
116
+ 1. ```b.attach_kprobe(event: b.get_syscall_fnname("clone"), fn_name: "hello")```: Creates a kprobe for the kernel clone system call function, which will execute our defined hello() function. You can call attach_kprobe() more than once, and attach your C function to multiple kernel functions.
117
+
118
+ 1. ```b.trace_fields do |...|```: Loop wirth a fixed set of fields from trace_pipe(without blcok, this method just returns the same set of fields). Similar to trace_print(), this is handy for hacking, but for real tooling we should switch to BPF_PERF_OUTPUT().
119
+
120
+ ### Lesson 4. sync_timing.py
121
+
122
+ Remember the days of sysadmins typing ```sync``` three times on a slow console before ```reboot```, to give the first asynchronous sync time to complete? Then someone thought ```sync;sync;sync``` was clever, to run them all on one line, which became industry practice despite defeating the original purpose! And then sync became synchronous, so more reasons it was silly. Anyway.
123
+
124
+ The following example times how quickly the ```do_sync``` function is called, and prints output if it has been called more recently than one second ago. A ```sync;sync;sync``` will print output for the 2nd and 3rd sync's:
125
+
126
+ ```
127
+ # ./examples/tracing/sync_timing.py
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
+ This program is [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
+ Things to learn (all in C):
179
+
180
+ 1. ```bpf_ktime_get_ns()```: Returns the time as nanoseconds.
181
+ 1. ```BPF_HASH(last)```: Creates a BPF map object that is a hash (associative array), called "last". We didn't specify any further arguments, so it defaults to key and value types of u64.
182
+ 1. ```key = 0```: We'll only store one key/value pair in this hash, where the key is hardwired to zero.
183
+ 1. ```last.lookup(&key)```: Lookup the key in the hash, and return a pointer to its value if it exists, else NULL. We pass the key in as an address to a pointer.
184
+ 1. ```last.delete(&key)```: Delete the key from the hash. This is currently required because of [a kernel bug in `.update()`](https://git.kernel.org/cgit/linux/kernel/git/davem/net.git/commit/?id=a6ed3ea65d9868fdf9eff84e6fe4f666b8d14b02).
185
+ 1. ```last.update(&key, &ts)```: Associate the value in the 2nd argument to the key, overwriting any previous value. This records the timestamp.
186
+
187
+ *Note for RbBCC developers:* Type of `trace_fields` return values differ from python's This should be fixed.
188
+
189
+ ### Lesson 5. sync_count.py
190
+
191
+ Modify the sync_timing.rb program (prior lesson) to store the count of all kernel sync system calls (both fast and slow), and print it with the output. This count can be recorded in the BPF program by adding a new key index to the existing hash.
192
+
193
+ One of sample implementation is at [answers/05-sync_count.rb](answers/05-sync_count.rb).
194
+
195
+ ### Lesson 6. disksnoop.rb
196
+
197
+ Browse the [answers/06-disksnoop.rb](answers/06-disksnoop.rb) program to see what is new. Here is some sample output:
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
+ And a code snippet:
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
+ Things to learn:
251
+
252
+ 1. ```REQ_WRITE```: We're defining a kernel constant in the Ruby program because we'll use it there later. If we were using REQ_WRITE in the BPF program, it should just work (without needing to be defined) with the appropriate #includes.
253
+ 1. ```trace_start(struct pt_regs *ctx, struct request *req)```: This function will later be attached to kprobes. The arguments to kprobe functions are ```struct pt_regs *ctx```, for registers and BPF context, and then the actual arguments to the function. We'll attach this to blk_start_request(), where the first argument is ```struct request *```.
254
+ 1. ```start.update(&req, &ts)```: We're using the pointer to the request struct as a key in our hash. What? This is commonplace in tracing. Pointers to structs turn out to be great keys, as they are unique: two structs can't have the same pointer address. (Just be careful about when it gets free'd and reused.) So what we're really doing is tagging the request struct, which describes the disk I/O, with our own timestamp, so that we can time it. There's two common keys used for storing timestamps: pointers to structs, and, thread IDs (for timing function entry to return).
255
+ 1. ```req->__data_len```: We're dereferencing members of ```struct request```. See its definition in the kernel source for what members are there. bcc actually rewrites these expressions to be a series of ```bpf_probe_read()``` calls. Sometimes bcc can't handle a complex dereference, and you need to call ```bpf_probe_read()``` directly.
256
+
257
+ This is a pretty interesting program, and if you can understand all the code, you'll understand many important basics. We're still using the bpf_trace_printk() hack, so let's fix that next.
258
+
259
+ ### Lesson 7. hello_perf_output.rb
260
+
261
+ Let's finally stop using bpf_trace_printk() and use the proper BPF_PERF_OUTPUT() interface. This will also mean we stop getting the free trace_field() members like PID and timestamp, and will need to fetch them directly. Sample output while commands are run in another session:
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
+ Code is [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
+ Things to learn:
338
+
339
+ 1. ```struct data_t```: This defines the C struct we'll use to pass data from kernel to user space.
340
+ 1. ```BPF_PERF_OUTPUT(events)```: This names our output channel "events".
341
+ 1. ```struct data_t data = {};```: Create an empty data_t struct that we'll then populate.
342
+ 1. ```bpf_get_current_pid_tgid()```: Returns the process ID in the lower 32 bits (kernel's view of the PID, which in user space is usually presented as the thread ID), and the thread group ID in the upper 32 bits (what user space often thinks of as the PID). By directly setting this to a u32, we discard the upper 32 bits. Should you be presenting the PID or the TGID? For a multi-threaded app, the TGID will be the same, so you need the PID to differentiate them, if that's what you want. It's also a question of expectations for the end user.
343
+ 1. ```bpf_get_current_comm()```: Populates the first argument address with the current process name.
344
+ 1. ```events.perf_submit()```: Submit the event for user space to read via a perf ring buffer.
345
+ 1. ```print_event = lambda { ... }```: Define a Ruby proc(lambda) that will handle reading events from the ```events``` stream; BTW unlike Python, you can pass block directory to method `open_perf_buffer`.
346
+ 1. ```b["events"].event(data)```: Now get the event as a Ruby object, auto-generated from the C declaration.
347
+ 1. ```b["events"].open_perf_buffer(&print_event)```: Associate the proc ```print_event``` with the ```events``` stream.
348
+ 1. ```loop { b.perf_buffer_poll() }```: Block waiting for events.
349
+
350
+ ### Lesson 8. sync_perf_output.rb
351
+
352
+ Rewrite sync_timing.rb, from a prior lesson, to use ```BPF_PERF_OUTPUT```.
353
+
354
+ Example is at [answers/08-sync_perf_output.rb](answers/08-sync_perf_output.rb).
355
+
356
+ ### Lesson 9. bitehist.rb
357
+
358
+ The following tool records a histogram of disk I/O sizes. Sample output:
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
+ Code is [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
+ A recap from earlier lessons:
410
+
411
+ - ```kprobe__```: This prefix means the rest will be treated as a kernel function name that will be instrumented using kprobe.
412
+ - ```struct pt_regs *ctx, struct request *req```: Arguments to kprobe. The ```ctx``` is registers and BPF context, the ```req``` is the first argument to the instrumented function: ```blk_account_io_completion()```.
413
+ - ```req->__data_len```: Dereferencing that member.
414
+
415
+ New things to learn:
416
+
417
+ 1. ```BPF_HISTOGRAM(dist)```: Defines a BPF map object that is a histogram, and names it "dist".
418
+ 1. ```dist.increment()```: Increments the histogram bucket index provided as first argument by one by default. Optionally, custom increments can be passed as the second argument.
419
+ 1. ```bpf_log2l()```: Returns the log-2 of the provided value. This becomes the index of our histogram, so that we're constructing a power-of-2 histogram.
420
+ 1. ```b["dist"].print_log2_hist("kbytes")```: Prints the "dist" histogram as power-of-2, with a column header of "kbytes". The only data transferred from kernel to user space is the bucket counts, making this efficient.
421
+
422
+ ### Lesson 10. disklatency.rb
423
+
424
+ Write a program that times disk I/O, and prints a histogram of their latency. Disk I/O instrumentation and timing can be found in the disksnoop.rb program from a prior lesson, and histogram code can be found in bitehist.rb from a prior lesson.
425
+
426
+ Example is at [answers/10-disklatency.rb](answers/10-disklatency.rb).
427
+
428
+ ### Lesson 11. vfsreadlat.rb
429
+
430
+ This example is split into separate Python and C files. Example output:
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
+ Browse the code in [answers/11-vfsreadlat.rb](answers/11-vfsreadlat.rb) and [answers/11-vfsreadlat.c](answers/11-vfsreadlat.c). Things to learn:
471
+
472
+ 1. ```b = BCC.new(src_file: "vfsreadlat.c")```: Read the BPF C program from a separate source file.
473
+ 1. ```b.attach_kretprobe(event: "vfs_read", fn_name: "do_return")```: Attaches the BPF C function ```do_return()``` to the return of the kernel function ```vfs_read()```. This is a kretprobe: instrumenting the return from a function, rather than its entry.
474
+ 1. ```b["dist"].clear()```: Clears the histogram.
475
+
476
+ ### Lesson 12. urandomread.rb
477
+
478
+ Tracing while a ```dd if=/dev/urandom of=/dev/null bs=8k count=5``` is run:
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
+ Hah! I caught smtp by accident. Code is [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
+ Things to learn:
521
+
522
+ 1. ```TRACEPOINT_PROBE(random, urandom_read)```: Instrument the kernel tracepoint ```random:urandom_read```. These have a stable API, and thus are recommend to use instead of kprobes, wherever possible. You can run ```perf list``` for a list of tracepoints. Linux >= 4.7 is required to attach BPF programs to tracepoints.
523
+ 1. ```args->got_bits```: ```args``` is auto-populated to be a structure of the tracepoint arguments. The comment above says where you can see that structure. Eg:
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
+ In this case, we were printing the ```got_bits``` member.
543
+
544
+ ### Lesson 13. disksnoop.rb fixed
545
+
546
+ Convert disksnoop.rb from a previous lesson to use the ```block:block_rq_issue``` and ```block:block_rq_complete``` tracepoints.
547
+
548
+ Example is at [answers/13-disksnoop_fixed.rb](answers/13-disksnoop_fixed.rb).
549
+
550
+
551
+ ### Lesson 14. strlen_count.rb
552
+
553
+ This program instruments a user-level function, the ```strlen()``` library function, and frequency counts its string argument. Example output:
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
+ These are various strings that are being processed by this library function while tracing, along with their frequency counts. ```strlen()``` was called on "LC_ALL" 12 times, for example.
578
+
579
+ Code is [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
+ Things to learn:
632
+
633
+ 1. ```PT_REGS_PARM1(ctx)```: This fetches the first argument to ```strlen()```, which is the string.
634
+ 1. ```b.attach_uprobe(name: "c", sym: "strlen", fn_name: "count")```: Attach to library "c" (if this is the main program, use its pathname), instrument the user-level function ```strlen()```, and on execution call our C function ```count()```.
635
+ 1. For ```BPF_HASH```, you should call ```k/v.to_bcc_value``` to iterate in Ruby block. This behavior is Ruby specific and would be changed in the future.
636
+
637
+ ### Lesson 15. nodejs_http_server.rb
638
+
639
+ This program instruments a user statically-defined tracing (USDT) probe, which is the user-level version of a kernel tracepoint. Sample output:
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
+ Example code from [answers/15-nodejs_http_server.rb](answers/15-nodejs_http_server.rb)
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
+ Things to learn:
688
+
689
+ 1. ```bpf_usdt_readarg(6, ctx, &addr)```: Read the address of argument 6 from the USDT probe into ```addr```.
690
+ 1. ```bpf_probe_read(&path, sizeof(path), (void *)addr)```: Now the string ```addr``` points to into our ```path``` variable.
691
+ 1. ```u = USDT.new(pid: pid.to_i)```: Initialize USDT tracing for the given PID.
692
+ 1. ```u.enable_probe(probe: "http__server__request", fn_name: "do_trace")```: Attach our ```do_trace()``` BPF C function to the Node.js ```http__server__request``` USDT probe.
693
+ 1. ```b = BCC.new(text: bpf_text, usdt_contexts: [u])```: Need to pass in our USDT object, ```u```, to BPF object creation.
694
+
695
+ ### Lesson 16. task_switch.c
696
+
697
+ This is an older tutorial included as a bonus lesson. Use this for recap and to reinforce what you've already learned.
698
+
699
+ This is a slightly more complex tracing example than Hello World. This program
700
+ will be invoked for every task change in the kernel, and record in a BPF map
701
+ the new and old pids.
702
+
703
+ The C program below introduces a new concept: the prev argument. This
704
+ argument is treated specially by the BCC frontend, such that accesses
705
+ to this variable are read from the saved context that is passed by the
706
+ kprobe infrastructure. The prototype of the args starting from
707
+ position 1 should match the prototype of the kernel function being
708
+ kprobed. If done so, the program will have seamless access to the
709
+ function parameters.
710
+
711
+ ```c
712
+ #include <uapi/linux/ptrace.h>
713
+ #include <linux/sched.h>
714
+
715
+ struct key_t {
716
+ u32 prev_pid;
717
+ u32 curr_pid;
718
+ };
719
+
720
+ BPF_HASH(stats, struct key_t, u64, 1024);
721
+ int count_sched(struct pt_regs *ctx, struct task_struct *prev) {
722
+ struct key_t key = {};
723
+ u64 zero = 0, *val;
724
+
725
+ key.curr_pid = bpf_get_current_pid_tgid();
726
+ key.prev_pid = prev->pid;
727
+
728
+ // could also use `stats.increment(key);`
729
+ val = stats.lookup_or_try_init(&key, &zero);
730
+ if (val) {
731
+ (*val)++;
732
+ }
733
+ return 0;
734
+ }
735
+ ```
736
+
737
+ The userspace component loads the file shown above, and attaches it to the
738
+ `finish_task_switch` kernel function.
739
+ The `[]` operator of the BPF object gives access to each BPF_HASH in the
740
+ program, allowing pass-through access to the values residing in the kernel. Use
741
+ the object as you would any other python dict object: read, update, and deletes
742
+ are all allowed.
743
+
744
+ ```ruby
745
+ require 'rbbcc'
746
+ include RbBCC
747
+
748
+ b = BCC.new(src_file: "16-task_switch.c")
749
+ b.attach_kprobe(event: "finish_task_switch", fn_name: "count_sched")
750
+
751
+ # generate many schedule events
752
+ 100.times { sleep 0.01 }
753
+
754
+ b["stats"].each do |_k, v|
755
+ k = _k[0, 8].unpack("i! i!") # Handling pointer without type!!
756
+ puts("task_switch[%5d->%5d]=%u" % [k[0], k[1], v.to_bcc_value])
757
+ end
758
+ ```
759
+
760
+ These programs can be found in the files [answers/16-task_switch.c](answers/16-task_switch.c) and [answers/16-task_switch.rb](answers/16-task_switch.rb) respectively.
761
+
762
+ ### Lesson 17. Further Study
763
+
764
+ For further study, see [BCC original docs](https://github.com/iovisor/bcc/tree/master/docs) and Sasha Goldshtein's [linux-tracing-workshop](https://github.com/goldshtn/linux-tracing-workshop), which contains additional labs. There are also many tools in rbbcc/bcc /tools to study.
765
+
766
+ Please read [CONTRIBUTING-SCRIPTS.md](../CONTRIBUTING-SCRIPTS.md) if you wish to contrubite tools to rbbcc. At the bottom of the main [README.md](../README.md), you'll also find methods for contacting us. Good luck, and happy tracing!
767
+
768
+ ---
769
+
770
+ ## Networking
771
+
772
+ To do.