• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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