1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2 // Copyright (c) 2021 Wenbo Zhang
3 //
4 // Based on cachestat(8) from BCC by Brendan Gregg and Allan McAleavy.
5 // 8-Mar-2021 Wenbo Zhang Created this.
6 // 30-Jan-2023 Rong Tao Add kprobe and use fentry_can_attach() decide
7 // use fentry/kprobe
8 // 15-Feb-2023 Rong Tao Add tracepoint writeback_dirty_{page,folio}
9 #include <argp.h>
10 #include <signal.h>
11 #include <stdio.h>
12 #include <unistd.h>
13 #include <time.h>
14 #include <bpf/libbpf.h>
15 #include <bpf/bpf.h>
16 #include "cachestat.skel.h"
17 #include "trace_helpers.h"
18
19 static struct env {
20 time_t interval;
21 int times;
22 bool timestamp;
23 bool verbose;
24 } env = {
25 .interval = 1,
26 .times = 99999999,
27 };
28
29 static volatile bool exiting;
30
31 const char *argp_program_version = "cachestat 0.1";
32 const char *argp_program_bug_address =
33 "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
34 const char argp_program_doc[] =
35 "Count cache kernel function calls.\n"
36 "\n"
37 "USAGE: cachestat [--help] [-T] [interval] [count]\n"
38 "\n"
39 "EXAMPLES:\n"
40 " cachestat # shows hits and misses to the file system page cache\n"
41 " cachestat -T # include timestamps\n"
42 " cachestat 1 10 # print 1 second summaries, 10 times\n";
43
44 static const struct argp_option opts[] = {
45 { "timestamp", 'T', NULL, 0, "Print timestamp" },
46 { "verbose", 'v', NULL, 0, "Verbose debug output" },
47 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
48 {},
49 };
50
parse_arg(int key,char * arg,struct argp_state * state)51 static error_t parse_arg(int key, char *arg, struct argp_state *state)
52 {
53 static int pos_args;
54
55 switch (key) {
56 case 'h':
57 argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
58 break;
59 case 'v':
60 env.verbose = true;
61 break;
62 case 'T':
63 env.timestamp = true;
64 break;
65 case ARGP_KEY_ARG:
66 errno = 0;
67 if (pos_args == 0) {
68 env.interval = strtol(arg, NULL, 10);
69 if (errno) {
70 fprintf(stderr, "invalid internal\n");
71 argp_usage(state);
72 }
73 } else if (pos_args == 1) {
74 env.times = strtol(arg, NULL, 10);
75 if (errno) {
76 fprintf(stderr, "invalid times\n");
77 argp_usage(state);
78 }
79 } else {
80 fprintf(stderr,
81 "unrecognized positional argument: %s\n", arg);
82 argp_usage(state);
83 }
84 pos_args++;
85 break;
86 default:
87 return ARGP_ERR_UNKNOWN;
88 }
89 return 0;
90 }
91
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)92 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
93 {
94 if (level == LIBBPF_DEBUG && !env.verbose)
95 return 0;
96 return vfprintf(stderr, format, args);
97 }
98
sig_handler(int sig)99 static void sig_handler(int sig)
100 {
101 exiting = true;
102 }
103
get_meminfo(__u64 * buffers,__u64 * cached)104 static int get_meminfo(__u64 *buffers, __u64 *cached)
105 {
106 FILE *f;
107
108 f = fopen("/proc/meminfo", "r");
109 if (!f)
110 return -1;
111 if (fscanf(f,
112 "MemTotal: %*u kB\n"
113 "MemFree: %*u kB\n"
114 "MemAvailable: %*u kB\n"
115 "Buffers: %llu kB\n"
116 "Cached: %llu kB\n",
117 buffers, cached) != 2) {
118 fclose(f);
119 return -1;
120 }
121 fclose(f);
122 return 0;
123 }
124
main(int argc,char ** argv)125 int main(int argc, char **argv)
126 {
127 static const struct argp argp = {
128 .options = opts,
129 .parser = parse_arg,
130 .doc = argp_program_doc,
131 };
132 __u64 buffers, cached, mbd;
133 struct cachestat_bpf *obj;
134 __s64 total, misses, hits;
135 struct tm *tm;
136 float ratio;
137 char ts[32];
138 time_t t;
139 int err;
140
141 err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
142 if (err)
143 return err;
144
145 libbpf_set_print(libbpf_print_fn);
146
147 obj = cachestat_bpf__open();
148 if (!obj) {
149 fprintf(stderr, "failed to open BPF object\n");
150 return 1;
151 }
152
153 /**
154 * account_page_dirtied was renamed to folio_account_dirtied
155 * in kernel commit 203a31516616 ("mm/writeback: Add __folio_mark_dirty()")
156 */
157 if (fentry_can_attach("folio_account_dirtied", NULL)) {
158 err = bpf_program__set_attach_target(obj->progs.fentry_account_page_dirtied, 0,
159 "folio_account_dirtied");
160 if (err) {
161 fprintf(stderr, "failed to set attach target\n");
162 goto cleanup;
163 }
164 }
165 if (kprobe_exists("folio_account_dirtied")) {
166 bpf_program__set_autoload(obj->progs.kprobe_account_page_dirtied, false);
167 bpf_program__set_autoload(obj->progs.tracepoint__writeback_dirty_folio, false);
168 bpf_program__set_autoload(obj->progs.tracepoint__writeback_dirty_page, false);
169 } else if (kprobe_exists("account_page_dirtied")) {
170 bpf_program__set_autoload(obj->progs.kprobe_folio_account_dirtied, false);
171 bpf_program__set_autoload(obj->progs.tracepoint__writeback_dirty_folio, false);
172 bpf_program__set_autoload(obj->progs.tracepoint__writeback_dirty_page, false);
173 } else if (tracepoint_exists("writeback", "writeback_dirty_folio")) {
174 bpf_program__set_autoload(obj->progs.kprobe_account_page_dirtied, false);
175 bpf_program__set_autoload(obj->progs.kprobe_folio_account_dirtied, false);
176 bpf_program__set_autoload(obj->progs.tracepoint__writeback_dirty_page, false);
177 } else if (tracepoint_exists("writeback", "writeback_dirty_page")) {
178 bpf_program__set_autoload(obj->progs.kprobe_account_page_dirtied, false);
179 bpf_program__set_autoload(obj->progs.kprobe_folio_account_dirtied, false);
180 bpf_program__set_autoload(obj->progs.tracepoint__writeback_dirty_folio, false);
181 }
182
183 /* It fallbacks to kprobes when kernel does not support fentry. */
184 if (fentry_can_attach("folio_account_dirtied", NULL)
185 || fentry_can_attach("account_page_dirtied", NULL)) {
186 bpf_program__set_autoload(obj->progs.kprobe_account_page_dirtied, false);
187 } else {
188 bpf_program__set_autoload(obj->progs.fentry_account_page_dirtied, false);
189 }
190
191 if (fentry_can_attach("add_to_page_cache_lru", NULL)) {
192 bpf_program__set_autoload(obj->progs.kprobe_add_to_page_cache_lru, false);
193 } else {
194 bpf_program__set_autoload(obj->progs.fentry_add_to_page_cache_lru, false);
195 }
196
197 if (fentry_can_attach("mark_page_accessed", NULL)) {
198 bpf_program__set_autoload(obj->progs.kprobe_mark_page_accessed, false);
199 } else {
200 bpf_program__set_autoload(obj->progs.fentry_mark_page_accessed, false);
201 }
202
203 if (fentry_can_attach("mark_buffer_dirty", NULL)) {
204 bpf_program__set_autoload(obj->progs.kprobe_mark_buffer_dirty, false);
205 } else {
206 bpf_program__set_autoload(obj->progs.fentry_mark_buffer_dirty, false);
207 }
208
209 err = cachestat_bpf__load(obj);
210 if (err) {
211 fprintf(stderr, "failed to load BPF object\n");
212 goto cleanup;
213 }
214
215 if (!obj->bss) {
216 fprintf(stderr, "Memory-mapping BPF maps is supported starting from Linux 5.7, please upgrade.\n");
217 goto cleanup;
218 }
219
220 err = cachestat_bpf__attach(obj);
221 if (err) {
222 fprintf(stderr, "failed to attach BPF programs\n");
223 goto cleanup;
224 }
225
226 signal(SIGINT, sig_handler);
227
228 if (env.timestamp)
229 printf("%-8s ", "TIME");
230 printf("%8s %8s %8s %8s %12s %10s\n", "HITS", "MISSES", "DIRTIES",
231 "HITRATIO", "BUFFERS_MB", "CACHED_MB");
232
233 while (1) {
234 sleep(env.interval);
235
236 /* total = total cache accesses without counting dirties */
237 total = __atomic_exchange_n(&obj->bss->total, 0, __ATOMIC_RELAXED);
238 /* misses = total of add to lru because of read misses */
239 misses = __atomic_exchange_n(&obj->bss->misses, 0, __ATOMIC_RELAXED);
240 /* mbd = total of mark_buffer_dirty events */
241 mbd = __atomic_exchange_n(&obj->bss->mbd, 0, __ATOMIC_RELAXED);
242
243 if (total < 0)
244 total = 0;
245 if (misses < 0)
246 misses = 0;
247 hits = total - misses;
248 /*
249 * If hits are < 0, then its possible misses are overestimated
250 * due to possibly page cache read ahead adding more pages than
251 * needed. In this case just assume misses as total and reset
252 * hits.
253 */
254 if (hits < 0) {
255 misses = total;
256 hits = 0;
257 }
258 ratio = total > 0 ? hits * 1.0 / total : 0.0;
259 err = get_meminfo(&buffers, &cached);
260 if (err) {
261 fprintf(stderr, "failed to get meminfo: %d\n", err);
262 goto cleanup;
263 }
264 if (env.timestamp) {
265 time(&t);
266 tm = localtime(&t);
267 strftime(ts, sizeof(ts), "%H:%M:%S", tm);
268 printf("%-8s ", ts);
269 }
270 printf("%8lld %8lld %8llu %7.2f%% %12llu %10llu\n",
271 hits, misses, mbd, 100 * ratio,
272 buffers / 1024, cached / 1024);
273
274 if (exiting || --env.times == 0)
275 break;
276 }
277
278 cleanup:
279 cachestat_bpf__destroy(obj);
280 return err != 0;
281 }
282