1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2 // Copyright (c) 2020 Wenbo Zhang
3 //
4 // Based on biostacks(8) from BPF-Perf-Tools-Book by Brendan Gregg.
5 // 10-Aug-2020 Wenbo Zhang Created this.
6 #include <argp.h>
7 #include <signal.h>
8 #include <stdio.h>
9 #include <unistd.h>
10 #include <bpf/libbpf.h>
11 #include <bpf/bpf.h>
12 #include "biostacks.h"
13 #include "biostacks.skel.h"
14 #include "trace_helpers.h"
15
16 static struct env {
17 char *disk;
18 int duration;
19 bool milliseconds;
20 bool verbose;
21 } env = {
22 .duration = -1,
23 };
24
25 const char *argp_program_version = "biostacks 0.1";
26 const char *argp_program_bug_address =
27 "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
28 const char argp_program_doc[] =
29 "Tracing block I/O with init stacks.\n"
30 "\n"
31 "USAGE: biostacks [--help] [-d DISK] [-m] [duration]\n"
32 "\n"
33 "EXAMPLES:\n"
34 " biostacks # trace block I/O with init stacks.\n"
35 " biostacks 1 # trace for 1 seconds only\n"
36 " biostacks -d sdc # trace sdc only\n";
37
38 static const struct argp_option opts[] = {
39 { "disk", 'd', "DISK", 0, "Trace this disk only" },
40 { "milliseconds", 'm', NULL, 0, "Millisecond histogram" },
41 { "verbose", 'v', NULL, 0, "Verbose debug output" },
42 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
43 {},
44 };
45
parse_arg(int key,char * arg,struct argp_state * state)46 static error_t parse_arg(int key, char *arg, struct argp_state *state)
47 {
48 static int pos_args;
49
50 switch (key) {
51 case 'h':
52 argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
53 break;
54 case 'v':
55 env.verbose = true;
56 break;
57 case 'd':
58 env.disk = arg;
59 if (strlen(arg) + 1 > DISK_NAME_LEN) {
60 fprintf(stderr, "invaild disk name: too long\n");
61 argp_usage(state);
62 }
63 break;
64 case 'm':
65 env.milliseconds = true;
66 break;
67 case ARGP_KEY_ARG:
68 if (pos_args++) {
69 fprintf(stderr,
70 "unrecognized positional argument: %s\n", arg);
71 argp_usage(state);
72 }
73 errno = 0;
74 env.duration = strtoll(arg, NULL, 10);
75 if (errno || env.duration <= 0) {
76 fprintf(stderr, "invalid delay (in us): %s\n", arg);
77 argp_usage(state);
78 }
79 break;
80 default:
81 return ARGP_ERR_UNKNOWN;
82 }
83 return 0;
84 }
85
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)86 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
87 {
88 if (level == LIBBPF_DEBUG && !env.verbose)
89 return 0;
90 return vfprintf(stderr, format, args);
91 }
92
sig_handler(int sig)93 static void sig_handler(int sig)
94 {
95 }
96
97 static
print_map(struct ksyms * ksyms,struct partitions * partitions,int fd)98 void print_map(struct ksyms *ksyms, struct partitions *partitions, int fd)
99 {
100 const char *units = env.milliseconds ? "msecs" : "usecs";
101 struct rqinfo lookup_key = {}, next_key;
102 const struct partition *partition;
103 const struct ksym *ksym;
104 int num_stack, i, err;
105 struct hist hist;
106
107 while (!bpf_map_get_next_key(fd, &lookup_key, &next_key)) {
108 err = bpf_map_lookup_elem(fd, &next_key, &hist);
109 if (err < 0) {
110 fprintf(stderr, "failed to lookup hist: %d\n", err);
111 return;
112 }
113 partition = partitions__get_by_dev(partitions, next_key.dev);
114 printf("%-14.14s %-6d %-7s\n",
115 next_key.comm, next_key.pid,
116 partition ? partition->name : "Unknown");
117 num_stack = next_key.kern_stack_size /
118 sizeof(next_key.kern_stack[0]);
119 for (i = 0; i < num_stack; i++) {
120 ksym = ksyms__map_addr(ksyms, next_key.kern_stack[i]);
121 printf("%s\n", ksym ? ksym->name : "Unknown");
122 }
123 print_log2_hist(hist.slots, MAX_SLOTS, units);
124 printf("\n");
125 lookup_key = next_key;
126 }
127
128 return;
129 }
130
has_block_io_tracepoints(void)131 static bool has_block_io_tracepoints(void)
132 {
133 return tracepoint_exists("block", "block_io_start") &&
134 tracepoint_exists("block", "block_io_done");
135 }
136
disable_block_io_tracepoints(struct biostacks_bpf * obj)137 static void disable_block_io_tracepoints(struct biostacks_bpf *obj)
138 {
139 bpf_program__set_autoload(obj->progs.block_io_start, false);
140 bpf_program__set_autoload(obj->progs.block_io_done, false);
141 }
142
disable_blk_account_io_fentry(struct biostacks_bpf * obj)143 static void disable_blk_account_io_fentry(struct biostacks_bpf *obj)
144 {
145 bpf_program__set_autoload(obj->progs.blk_account_io_start, false);
146 bpf_program__set_autoload(obj->progs.blk_account_io_done, false);
147 }
148
blk_account_io_set_attach_target(struct biostacks_bpf * obj)149 static void blk_account_io_set_attach_target(struct biostacks_bpf *obj)
150 {
151 if (fentry_can_attach("blk_account_io_start", NULL)) {
152 bpf_program__set_attach_target(obj->progs.blk_account_io_start,
153 0, "blk_account_io_start");
154 bpf_program__set_attach_target(obj->progs.blk_account_io_done,
155 0, "blk_account_io_done");
156 } else {
157 bpf_program__set_attach_target(obj->progs.blk_account_io_start,
158 0, "__blk_account_io_start");
159 bpf_program__set_attach_target(obj->progs.blk_account_io_done,
160 0, "__blk_account_io_done");
161 }
162 }
163
main(int argc,char ** argv)164 int main(int argc, char **argv)
165 {
166 struct partitions *partitions = NULL;
167 const struct partition *partition;
168 static const struct argp argp = {
169 .options = opts,
170 .parser = parse_arg,
171 .doc = argp_program_doc,
172 };
173 struct ksyms *ksyms = NULL;
174 struct biostacks_bpf *obj;
175 int err;
176
177 err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
178 if (err)
179 return err;
180
181 libbpf_set_print(libbpf_print_fn);
182
183 obj = biostacks_bpf__open();
184 if (!obj) {
185 fprintf(stderr, "failed to open BPF object\n");
186 return 1;
187 }
188
189 partitions = partitions__load();
190 if (!partitions) {
191 fprintf(stderr, "failed to load partitions info\n");
192 goto cleanup;
193 }
194
195 /* initialize global data (filtering options) */
196 if (env.disk) {
197 partition = partitions__get_by_name(partitions, env.disk);
198 if (!partition) {
199 fprintf(stderr, "invaild partition name: not exist\n");
200 goto cleanup;
201 }
202 obj->rodata->filter_dev = true;
203 obj->rodata->targ_dev = partition->dev;
204 }
205
206 obj->rodata->targ_ms = env.milliseconds;
207
208 if (has_block_io_tracepoints())
209 disable_blk_account_io_fentry(obj);
210 else {
211 disable_block_io_tracepoints(obj);
212 blk_account_io_set_attach_target(obj);
213 }
214
215 ksyms = ksyms__load();
216 if (!ksyms) {
217 fprintf(stderr, "failed to load kallsyms\n");
218 goto cleanup;
219 }
220 if (!ksyms__get_symbol(ksyms, "blk_account_io_merge_bio"))
221 bpf_program__set_autoload(obj->progs.blk_account_io_merge_bio, false);
222
223 err = biostacks_bpf__load(obj);
224 if (err) {
225 fprintf(stderr, "failed to load BPF object: %d\n", err);
226 goto cleanup;
227 }
228
229 err = biostacks_bpf__attach(obj);
230 if (err) {
231 fprintf(stderr, "failed to attach BPF programs: %d\n", err);
232 goto cleanup;
233 }
234
235 signal(SIGINT, sig_handler);
236
237 printf("Tracing block I/O with init stacks. Hit Ctrl-C to end.\n");
238 sleep(env.duration);
239 print_map(ksyms, partitions, bpf_map__fd(obj->maps.hists));
240
241 cleanup:
242 biostacks_bpf__destroy(obj);
243 ksyms__free(ksyms);
244 partitions__free(partitions);
245
246 return err != 0;
247 }
248