• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2 // Copyright (c) 2020 Wenbo Zhang
3 //
4 // Based on biopattern(8) from BPF-Perf-Tools-Book by Brendan Gregg.
5 // 17-Jun-2020   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 "biopattern.h"
14 #include "biopattern.skel.h"
15 #include "trace_helpers.h"
16 
17 static struct env {
18 	char *disk;
19 	time_t interval;
20 	bool timestamp;
21 	bool verbose;
22 	int times;
23 } env = {
24 	.interval = 99999999,
25 	.times = 99999999,
26 };
27 
28 static volatile bool exiting;
29 
30 const char *argp_program_version = "biopattern 0.1";
31 const char *argp_program_bug_address =
32 	"https://github.com/iovisor/bcc/tree/master/libbpf-tools";
33 const char argp_program_doc[] =
34 "Show block device I/O pattern.\n"
35 "\n"
36 "USAGE: biopattern [--help] [-T] [-d DISK] [interval] [count]\n"
37 "\n"
38 "EXAMPLES:\n"
39 "    biopattern              # show block I/O pattern\n"
40 "    biopattern 1 10         # print 1 second summaries, 10 times\n"
41 "    biopattern -T 1         # 1s summaries with timestamps\n"
42 "    biopattern -d sdc       # trace sdc only\n";
43 
44 static const struct argp_option opts[] = {
45 	{ "timestamp", 'T', NULL, 0, "Include timestamp on output" },
46 	{ "disk",  'd', "DISK",  0, "Trace this disk only" },
47 	{ "verbose", 'v', NULL, 0, "Verbose debug output" },
48 	{ NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
49 	{},
50 };
51 
parse_arg(int key,char * arg,struct argp_state * state)52 static error_t parse_arg(int key, char *arg, struct argp_state *state)
53 {
54 	static int pos_args;
55 
56 	switch (key) {
57 	case 'h':
58 		argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
59 		break;
60 	case 'v':
61 		env.verbose = true;
62 		break;
63 	case 'd':
64 		env.disk = arg;
65 		if (strlen(arg) + 1 > DISK_NAME_LEN) {
66 			fprintf(stderr, "invaild disk name: too long\n");
67 			argp_usage(state);
68 		}
69 		break;
70 	case 'T':
71 		env.timestamp = true;
72 		break;
73 	case ARGP_KEY_ARG:
74 		errno = 0;
75 		if (pos_args == 0) {
76 			env.interval = strtol(arg, NULL, 10);
77 			if (errno) {
78 				fprintf(stderr, "invalid internal\n");
79 				argp_usage(state);
80 			}
81 		} else if (pos_args == 1) {
82 			env.times = strtol(arg, NULL, 10);
83 			if (errno) {
84 				fprintf(stderr, "invalid times\n");
85 				argp_usage(state);
86 			}
87 		} else {
88 			fprintf(stderr,
89 				"unrecognized positional argument: %s\n", arg);
90 			argp_usage(state);
91 		}
92 		pos_args++;
93 		break;
94 	default:
95 		return ARGP_ERR_UNKNOWN;
96 	}
97 	return 0;
98 }
99 
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)100 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
101 {
102 	if (level == LIBBPF_DEBUG && !env.verbose)
103 		return 0;
104 	return vfprintf(stderr, format, args);
105 }
106 
sig_handler(int sig)107 static void sig_handler(int sig)
108 {
109 	exiting = true;
110 }
111 
print_map(struct bpf_map * counters,struct partitions * partitions)112 static int print_map(struct bpf_map *counters, struct partitions *partitions)
113 {
114 	__u32 total, lookup_key = -1, next_key;
115 	int err, fd = bpf_map__fd(counters);
116 	const struct partition *partition;
117 	struct counter counter;
118 	struct tm *tm;
119 	char ts[32];
120 	time_t t;
121 
122 	while (!bpf_map_get_next_key(fd, &lookup_key, &next_key)) {
123 		err = bpf_map_lookup_elem(fd, &next_key, &counter);
124 		if (err < 0) {
125 			fprintf(stderr, "failed to lookup counters: %d\n", err);
126 			return -1;
127 		}
128 		lookup_key = next_key;
129 		total = counter.sequential + counter.random;
130 		if (!total)
131 			continue;
132 		if (env.timestamp) {
133 			time(&t);
134 			tm = localtime(&t);
135 			strftime(ts, sizeof(ts), "%H:%M:%S", tm);
136 			printf("%-9s ", ts);
137 		}
138 		partition = partitions__get_by_dev(partitions, next_key);
139 		printf("%-7s %5ld %5ld %8d %10lld\n",
140 			partition ? partition->name : "Unknown",
141 			counter.random * 100L / total,
142 			counter.sequential * 100L / total, total,
143 			counter.bytes / 1024);
144 	}
145 
146 	lookup_key = -1;
147 	while (!bpf_map_get_next_key(fd, &lookup_key, &next_key)) {
148 		err = bpf_map_delete_elem(fd, &next_key);
149 		if (err < 0) {
150 			fprintf(stderr, "failed to cleanup counters: %d\n", err);
151 			return -1;
152 		}
153 		lookup_key = next_key;
154 	}
155 
156 	return 0;
157 }
158 
main(int argc,char ** argv)159 int main(int argc, char **argv)
160 {
161 	struct partitions *partitions = NULL;
162 	const struct partition *partition;
163 	static const struct argp argp = {
164 		.options = opts,
165 		.parser = parse_arg,
166 		.doc = argp_program_doc,
167 	};
168 	struct biopattern_bpf *obj;
169 	int err;
170 
171 	err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
172 	if (err)
173 		return err;
174 
175 	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
176 	libbpf_set_print(libbpf_print_fn);
177 
178 	obj = biopattern_bpf__open();
179 	if (!obj) {
180 		fprintf(stderr, "failed to open BPF object\n");
181 		return 1;
182 	}
183 
184 	partitions = partitions__load();
185 	if (!partitions) {
186 		fprintf(stderr, "failed to load partitions info\n");
187 		goto cleanup;
188 	}
189 
190 	/* initialize global data (filtering options) */
191 	if (env.disk) {
192 		partition = partitions__get_by_name(partitions, env.disk);
193 		if (!partition) {
194 			fprintf(stderr, "invaild partition name: not exist\n");
195 			goto cleanup;
196 		}
197 		obj->rodata->filter_dev = true;
198 		obj->rodata->targ_dev = partition->dev;
199 	}
200 
201 	err = biopattern_bpf__load(obj);
202 	if (err) {
203 		fprintf(stderr, "failed to load BPF object: %d\n", err);
204 		goto cleanup;
205 	}
206 
207 	err = biopattern_bpf__attach(obj);
208 	if (err) {
209 		fprintf(stderr, "failed to attach BPF programs\n");
210 		goto cleanup;
211 	}
212 
213 	signal(SIGINT, sig_handler);
214 
215 	printf("Tracing block device I/O requested seeks... Hit Ctrl-C to "
216 		"end.\n");
217 	if (env.timestamp)
218 		printf("%-9s ", "TIME");
219 	printf("%-7s %5s %5s %8s %10s\n", "DISK", "%RND", "%SEQ",
220 		"COUNT", "KBYTES");
221 
222 	/* main: poll */
223 	while (1) {
224 		sleep(env.interval);
225 
226 		err = print_map(obj->maps.counters, partitions);
227 		if (err)
228 			break;
229 
230 		if (exiting || --env.times == 0)
231 			break;
232 	}
233 
234 cleanup:
235 	biopattern_bpf__destroy(obj);
236 	partitions__free(partitions);
237 
238 	return err != 0;
239 }
240