1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright (c) 2020 Anton Protopopov
3 //
4 // Based on vfsstat(8) from BCC by Brendan Gregg
5 #include <argp.h>
6 #include <unistd.h>
7 #include <time.h>
8 #include <bpf/bpf.h>
9 #include "vfsstat.h"
10 #include "vfsstat.skel.h"
11 #include "btf_helpers.h"
12 #include "trace_helpers.h"
13
14 const char *argp_program_version = "vfsstat 0.1";
15 const char *argp_program_bug_address =
16 "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
17 static const char argp_program_doc[] =
18 "\nvfsstat: Count some VFS calls\n"
19 "\n"
20 "EXAMPLES:\n"
21 " vfsstat # interval one second\n"
22 " vfsstat 5 3 # interval five seconds, three output lines\n";
23 static char args_doc[] = "[interval [count]]";
24
25 static const struct argp_option opts[] = {
26 { "verbose", 'v', NULL, 0, "Verbose debug output" },
27 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
28 {},
29 };
30
31 static struct env {
32 bool verbose;
33 int count;
34 int interval;
35 } env = {
36 .interval = 1, /* once a second */
37 };
38
parse_arg(int key,char * arg,struct argp_state * state)39 static error_t parse_arg(int key, char *arg, struct argp_state *state)
40 {
41 long interval;
42 long count;
43
44 switch (key) {
45 case 'h':
46 argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
47 break;
48 case 'v':
49 env.verbose = true;
50 break;
51 case ARGP_KEY_ARG:
52 switch (state->arg_num) {
53 case 0:
54 errno = 0;
55 interval = strtol(arg, NULL, 10);
56 if (errno || interval <= 0 || interval > INT_MAX) {
57 fprintf(stderr, "invalid interval: %s\n", arg);
58 argp_usage(state);
59 }
60 env.interval = interval;
61 break;
62 case 1:
63 errno = 0;
64 count = strtol(arg, NULL, 10);
65 if (errno || count < 0 || count > INT_MAX) {
66 fprintf(stderr, "invalid count: %s\n", arg);
67 argp_usage(state);
68 }
69 env.count = count;
70 break;
71 default:
72 argp_usage(state);
73 break;
74 }
75 break;
76 default:
77 return ARGP_ERR_UNKNOWN;
78 }
79 return 0;
80 }
81
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)82 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
83 {
84 if (level == LIBBPF_DEBUG && !env.verbose)
85 return 0;
86 return vfprintf(stderr, format, args);
87 }
88
strftime_now(char * s,size_t max,const char * format)89 static const char *strftime_now(char *s, size_t max, const char *format)
90 {
91 struct tm *tm;
92 time_t t;
93
94 t = time(NULL);
95 tm = localtime(&t);
96 if (tm == NULL) {
97 fprintf(stderr, "localtime: %s\n", strerror(errno));
98 return "<failed>";
99 }
100 if (strftime(s, max, format, tm) == 0) {
101 fprintf(stderr, "strftime error\n");
102 return "<failed>";
103 }
104 return s;
105 }
106
107 static const char *stat_types_names[] = {
108 [S_READ] = "READ",
109 [S_WRITE] = "WRITE",
110 [S_FSYNC] = "FSYNC",
111 [S_OPEN] = "OPEN",
112 [S_CREATE] = "CREATE",
113 };
114
print_header(void)115 static void print_header(void)
116 {
117 int i;
118
119 printf("%-8s ", "TIME");
120 for (i = 0; i < S_MAXSTAT; i++)
121 printf(" %6s/s", stat_types_names[i]);
122 printf("\n");
123 }
124
print_and_reset_stats(__u64 stats[S_MAXSTAT])125 static void print_and_reset_stats(__u64 stats[S_MAXSTAT])
126 {
127 char s[16];
128 __u64 val;
129 int i;
130
131 printf("%-8s: ", strftime_now(s, sizeof(s), "%H:%M:%S"));
132 for (i = 0; i < S_MAXSTAT; i++) {
133 val = __atomic_exchange_n(&stats[i], 0, __ATOMIC_RELAXED);
134 printf(" %8llu", val / env.interval);
135 }
136 printf("\n");
137 }
138
main(int argc,char ** argv)139 int main(int argc, char **argv)
140 {
141 LIBBPF_OPTS(bpf_object_open_opts, open_opts);
142 static const struct argp argp = {
143 .options = opts,
144 .parser = parse_arg,
145 .doc = argp_program_doc,
146 .args_doc = args_doc,
147 };
148 struct vfsstat_bpf *skel;
149 int err;
150
151 err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
152 if (err)
153 return err;
154
155 libbpf_set_print(libbpf_print_fn);
156
157
158 err = ensure_core_btf(&open_opts);
159 if (err) {
160 fprintf(stderr, "failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err));
161 return 1;
162 }
163
164 skel = vfsstat_bpf__open();
165 if (!skel) {
166 fprintf(stderr, "failed to open BPF skelect\n");
167 return 1;
168 }
169
170 /* It fallbacks to kprobes when kernel does not support fentry. */
171 if (fentry_can_attach("vfs_read", NULL)) {
172 bpf_program__set_autoload(skel->progs.kprobe_vfs_read, false);
173 bpf_program__set_autoload(skel->progs.kprobe_vfs_write, false);
174 bpf_program__set_autoload(skel->progs.kprobe_vfs_fsync, false);
175 bpf_program__set_autoload(skel->progs.kprobe_vfs_open, false);
176 bpf_program__set_autoload(skel->progs.kprobe_vfs_create, false);
177 } else {
178 bpf_program__set_autoload(skel->progs.fentry_vfs_read, false);
179 bpf_program__set_autoload(skel->progs.fentry_vfs_write, false);
180 bpf_program__set_autoload(skel->progs.fentry_vfs_fsync, false);
181 bpf_program__set_autoload(skel->progs.fentry_vfs_open, false);
182 bpf_program__set_autoload(skel->progs.fentry_vfs_create, false);
183 }
184
185 err = vfsstat_bpf__load(skel);
186 if (err) {
187 fprintf(stderr, "failed to load BPF skelect: %d\n", err);
188 goto cleanup;
189 }
190
191 if (!skel->bss) {
192 fprintf(stderr, "Memory-mapping BPF maps is supported starting from Linux 5.7, please upgrade.\n");
193 goto cleanup;
194 }
195
196 err = vfsstat_bpf__attach(skel);
197 if (err) {
198 fprintf(stderr, "failed to attach BPF programs: %s\n",
199 strerror(-err));
200 goto cleanup;
201 }
202
203 print_header();
204 do {
205 sleep(env.interval);
206 print_and_reset_stats(skel->bss->stats);
207 } while (!env.count || --env.count);
208
209 cleanup:
210 vfsstat_bpf__destroy(skel);
211 cleanup_core_btf(&open_opts);
212
213 return err != 0;
214 }
215