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