xref: /linux/tools/testing/selftests/bpf/bench.c (revision 8eecf1c9929aef24e9e75280a39ed1ba3c64fb71)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2020 Facebook */
3 #define _GNU_SOURCE
4 #include <argp.h>
5 #include <linux/compiler.h>
6 #include <sys/time.h>
7 #include <sched.h>
8 #include <fcntl.h>
9 #include <pthread.h>
10 #include <sys/sysinfo.h>
11 #include <signal.h>
12 #include "bench.h"
13 #include "testing_helpers.h"
14 
15 struct env env = {
16 	.warmup_sec = 1,
17 	.duration_sec = 5,
18 	.affinity = false,
19 	.consumer_cnt = 1,
20 	.producer_cnt = 1,
21 };
22 
23 static int libbpf_print_fn(enum libbpf_print_level level,
24 		    const char *format, va_list args)
25 {
26 	if (level == LIBBPF_DEBUG && !env.verbose)
27 		return 0;
28 	return vfprintf(stderr, format, args);
29 }
30 
31 void setup_libbpf(void)
32 {
33 	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
34 	libbpf_set_print(libbpf_print_fn);
35 }
36 
37 void false_hits_report_progress(int iter, struct bench_res *res, long delta_ns)
38 {
39 	long total = res->false_hits  + res->hits + res->drops;
40 
41 	printf("Iter %3d (%7.3lfus): ",
42 	       iter, (delta_ns - 1000000000) / 1000.0);
43 
44 	printf("%ld false hits of %ld total operations. Percentage = %2.2f %%\n",
45 	       res->false_hits, total, ((float)res->false_hits / total) * 100);
46 }
47 
48 void false_hits_report_final(struct bench_res res[], int res_cnt)
49 {
50 	long total_hits = 0, total_drops = 0, total_false_hits = 0, total_ops = 0;
51 	int i;
52 
53 	for (i = 0; i < res_cnt; i++) {
54 		total_hits += res[i].hits;
55 		total_false_hits += res[i].false_hits;
56 		total_drops += res[i].drops;
57 	}
58 	total_ops = total_hits + total_false_hits + total_drops;
59 
60 	printf("Summary: %ld false hits of %ld total operations. ",
61 	       total_false_hits, total_ops);
62 	printf("Percentage =  %2.2f %%\n",
63 	       ((float)total_false_hits / total_ops) * 100);
64 }
65 
66 void hits_drops_report_progress(int iter, struct bench_res *res, long delta_ns)
67 {
68 	double hits_per_sec, drops_per_sec;
69 	double hits_per_prod;
70 
71 	hits_per_sec = res->hits / 1000000.0 / (delta_ns / 1000000000.0);
72 	hits_per_prod = hits_per_sec / env.producer_cnt;
73 	drops_per_sec = res->drops / 1000000.0 / (delta_ns / 1000000000.0);
74 
75 	printf("Iter %3d (%7.3lfus): ",
76 	       iter, (delta_ns - 1000000000) / 1000.0);
77 
78 	printf("hits %8.3lfM/s (%7.3lfM/prod), drops %8.3lfM/s, total operations %8.3lfM/s\n",
79 	       hits_per_sec, hits_per_prod, drops_per_sec, hits_per_sec + drops_per_sec);
80 }
81 
82 void hits_drops_report_final(struct bench_res res[], int res_cnt)
83 {
84 	int i;
85 	double hits_mean = 0.0, drops_mean = 0.0, total_ops_mean = 0.0;
86 	double hits_stddev = 0.0, drops_stddev = 0.0, total_ops_stddev = 0.0;
87 	double total_ops;
88 
89 	for (i = 0; i < res_cnt; i++) {
90 		hits_mean += res[i].hits / 1000000.0 / (0.0 + res_cnt);
91 		drops_mean += res[i].drops / 1000000.0 / (0.0 + res_cnt);
92 	}
93 	total_ops_mean = hits_mean + drops_mean;
94 
95 	if (res_cnt > 1)  {
96 		for (i = 0; i < res_cnt; i++) {
97 			hits_stddev += (hits_mean - res[i].hits / 1000000.0) *
98 				       (hits_mean - res[i].hits / 1000000.0) /
99 				       (res_cnt - 1.0);
100 			drops_stddev += (drops_mean - res[i].drops / 1000000.0) *
101 					(drops_mean - res[i].drops / 1000000.0) /
102 					(res_cnt - 1.0);
103 			total_ops = res[i].hits + res[i].drops;
104 			total_ops_stddev += (total_ops_mean - total_ops / 1000000.0) *
105 					(total_ops_mean - total_ops / 1000000.0) /
106 					(res_cnt - 1.0);
107 		}
108 		hits_stddev = sqrt(hits_stddev);
109 		drops_stddev = sqrt(drops_stddev);
110 		total_ops_stddev = sqrt(total_ops_stddev);
111 	}
112 	printf("Summary: hits %8.3lf \u00B1 %5.3lfM/s (%7.3lfM/prod), ",
113 	       hits_mean, hits_stddev, hits_mean / env.producer_cnt);
114 	printf("drops %8.3lf \u00B1 %5.3lfM/s, ",
115 	       drops_mean, drops_stddev);
116 	printf("total operations %8.3lf \u00B1 %5.3lfM/s\n",
117 	       total_ops_mean, total_ops_stddev);
118 }
119 
120 void ops_report_progress(int iter, struct bench_res *res, long delta_ns)
121 {
122 	double hits_per_sec, hits_per_prod;
123 
124 	hits_per_sec = res->hits / 1000000.0 / (delta_ns / 1000000000.0);
125 	hits_per_prod = hits_per_sec / env.producer_cnt;
126 
127 	printf("Iter %3d (%7.3lfus): ", iter, (delta_ns - 1000000000) / 1000.0);
128 
129 	printf("hits %8.3lfM/s (%7.3lfM/prod)\n", hits_per_sec, hits_per_prod);
130 }
131 
132 void ops_report_final(struct bench_res res[], int res_cnt)
133 {
134 	double hits_mean = 0.0, hits_stddev = 0.0;
135 	int i;
136 
137 	for (i = 0; i < res_cnt; i++)
138 		hits_mean += res[i].hits / 1000000.0 / (0.0 + res_cnt);
139 
140 	if (res_cnt > 1)  {
141 		for (i = 0; i < res_cnt; i++)
142 			hits_stddev += (hits_mean - res[i].hits / 1000000.0) *
143 				       (hits_mean - res[i].hits / 1000000.0) /
144 				       (res_cnt - 1.0);
145 
146 		hits_stddev = sqrt(hits_stddev);
147 	}
148 	printf("Summary: throughput %8.3lf \u00B1 %5.3lf M ops/s (%7.3lfM ops/prod), ",
149 	       hits_mean, hits_stddev, hits_mean / env.producer_cnt);
150 	printf("latency %8.3lf ns/op\n", 1000.0 / hits_mean * env.producer_cnt);
151 }
152 
153 const char *argp_program_version = "benchmark";
154 const char *argp_program_bug_address = "<bpf@vger.kernel.org>";
155 const char argp_program_doc[] =
156 "benchmark    Generic benchmarking framework.\n"
157 "\n"
158 "This tool runs benchmarks.\n"
159 "\n"
160 "USAGE: benchmark <bench-name>\n"
161 "\n"
162 "EXAMPLES:\n"
163 "    # run 'count-local' benchmark with 1 producer and 1 consumer\n"
164 "    benchmark count-local\n"
165 "    # run 'count-local' with 16 producer and 8 consumer thread, pinned to CPUs\n"
166 "    benchmark -p16 -c8 -a count-local\n";
167 
168 enum {
169 	ARG_PROD_AFFINITY_SET = 1000,
170 	ARG_CONS_AFFINITY_SET = 1001,
171 };
172 
173 static const struct argp_option opts[] = {
174 	{ "list", 'l', NULL, 0, "List available benchmarks"},
175 	{ "duration", 'd', "SEC", 0, "Duration of benchmark, seconds"},
176 	{ "warmup", 'w', "SEC", 0, "Warm-up period, seconds"},
177 	{ "producers", 'p', "NUM", 0, "Number of producer threads"},
178 	{ "consumers", 'c', "NUM", 0, "Number of consumer threads"},
179 	{ "verbose", 'v', NULL, 0, "Verbose debug output"},
180 	{ "affinity", 'a', NULL, 0, "Set consumer/producer thread affinity"},
181 	{ "prod-affinity", ARG_PROD_AFFINITY_SET, "CPUSET", 0,
182 	  "Set of CPUs for producer threads; implies --affinity"},
183 	{ "cons-affinity", ARG_CONS_AFFINITY_SET, "CPUSET", 0,
184 	  "Set of CPUs for consumer threads; implies --affinity"},
185 	{},
186 };
187 
188 extern struct argp bench_ringbufs_argp;
189 extern struct argp bench_bloom_map_argp;
190 extern struct argp bench_bpf_loop_argp;
191 extern struct argp bench_strncmp_argp;
192 
193 static const struct argp_child bench_parsers[] = {
194 	{ &bench_ringbufs_argp, 0, "Ring buffers benchmark", 0 },
195 	{ &bench_bloom_map_argp, 0, "Bloom filter map benchmark", 0 },
196 	{ &bench_bpf_loop_argp, 0, "bpf_loop helper benchmark", 0 },
197 	{ &bench_strncmp_argp, 0, "bpf_strncmp helper benchmark", 0 },
198 	{},
199 };
200 
201 static error_t parse_arg(int key, char *arg, struct argp_state *state)
202 {
203 	static int pos_args;
204 
205 	switch (key) {
206 	case 'v':
207 		env.verbose = true;
208 		break;
209 	case 'l':
210 		env.list = true;
211 		break;
212 	case 'd':
213 		env.duration_sec = strtol(arg, NULL, 10);
214 		if (env.duration_sec <= 0) {
215 			fprintf(stderr, "Invalid duration: %s\n", arg);
216 			argp_usage(state);
217 		}
218 		break;
219 	case 'w':
220 		env.warmup_sec = strtol(arg, NULL, 10);
221 		if (env.warmup_sec <= 0) {
222 			fprintf(stderr, "Invalid warm-up duration: %s\n", arg);
223 			argp_usage(state);
224 		}
225 		break;
226 	case 'p':
227 		env.producer_cnt = strtol(arg, NULL, 10);
228 		if (env.producer_cnt <= 0) {
229 			fprintf(stderr, "Invalid producer count: %s\n", arg);
230 			argp_usage(state);
231 		}
232 		break;
233 	case 'c':
234 		env.consumer_cnt = strtol(arg, NULL, 10);
235 		if (env.consumer_cnt <= 0) {
236 			fprintf(stderr, "Invalid consumer count: %s\n", arg);
237 			argp_usage(state);
238 		}
239 		break;
240 	case 'a':
241 		env.affinity = true;
242 		break;
243 	case ARG_PROD_AFFINITY_SET:
244 		env.affinity = true;
245 		if (parse_num_list(arg, &env.prod_cpus.cpus,
246 				   &env.prod_cpus.cpus_len)) {
247 			fprintf(stderr, "Invalid format of CPU set for producers.");
248 			argp_usage(state);
249 		}
250 		break;
251 	case ARG_CONS_AFFINITY_SET:
252 		env.affinity = true;
253 		if (parse_num_list(arg, &env.cons_cpus.cpus,
254 				   &env.cons_cpus.cpus_len)) {
255 			fprintf(stderr, "Invalid format of CPU set for consumers.");
256 			argp_usage(state);
257 		}
258 		break;
259 	case ARGP_KEY_ARG:
260 		if (pos_args++) {
261 			fprintf(stderr,
262 				"Unrecognized positional argument: %s\n", arg);
263 			argp_usage(state);
264 		}
265 		env.bench_name = strdup(arg);
266 		break;
267 	default:
268 		return ARGP_ERR_UNKNOWN;
269 	}
270 	return 0;
271 }
272 
273 static void parse_cmdline_args(int argc, char **argv)
274 {
275 	static const struct argp argp = {
276 		.options = opts,
277 		.parser = parse_arg,
278 		.doc = argp_program_doc,
279 		.children = bench_parsers,
280 	};
281 	if (argp_parse(&argp, argc, argv, 0, NULL, NULL))
282 		exit(1);
283 	if (!env.list && !env.bench_name) {
284 		argp_help(&argp, stderr, ARGP_HELP_DOC, "bench");
285 		exit(1);
286 	}
287 }
288 
289 static void collect_measurements(long delta_ns);
290 
291 static __u64 last_time_ns;
292 static void sigalarm_handler(int signo)
293 {
294 	long new_time_ns = get_time_ns();
295 	long delta_ns = new_time_ns - last_time_ns;
296 
297 	collect_measurements(delta_ns);
298 
299 	last_time_ns = new_time_ns;
300 }
301 
302 /* set up periodic 1-second timer */
303 static void setup_timer()
304 {
305 	static struct sigaction sigalarm_action = {
306 		.sa_handler = sigalarm_handler,
307 	};
308 	struct itimerval timer_settings = {};
309 	int err;
310 
311 	last_time_ns = get_time_ns();
312 	err = sigaction(SIGALRM, &sigalarm_action, NULL);
313 	if (err < 0) {
314 		fprintf(stderr, "failed to install SIGALRM handler: %d\n", -errno);
315 		exit(1);
316 	}
317 	timer_settings.it_interval.tv_sec = 1;
318 	timer_settings.it_value.tv_sec = 1;
319 	err = setitimer(ITIMER_REAL, &timer_settings, NULL);
320 	if (err < 0) {
321 		fprintf(stderr, "failed to arm interval timer: %d\n", -errno);
322 		exit(1);
323 	}
324 }
325 
326 static void set_thread_affinity(pthread_t thread, int cpu)
327 {
328 	cpu_set_t cpuset;
329 
330 	CPU_ZERO(&cpuset);
331 	CPU_SET(cpu, &cpuset);
332 	if (pthread_setaffinity_np(thread, sizeof(cpuset), &cpuset)) {
333 		fprintf(stderr, "setting affinity to CPU #%d failed: %d\n",
334 			cpu, errno);
335 		exit(1);
336 	}
337 }
338 
339 static int next_cpu(struct cpu_set *cpu_set)
340 {
341 	if (cpu_set->cpus) {
342 		int i;
343 
344 		/* find next available CPU */
345 		for (i = cpu_set->next_cpu; i < cpu_set->cpus_len; i++) {
346 			if (cpu_set->cpus[i]) {
347 				cpu_set->next_cpu = i + 1;
348 				return i;
349 			}
350 		}
351 		fprintf(stderr, "Not enough CPUs specified, need CPU #%d or higher.\n", i);
352 		exit(1);
353 	}
354 
355 	return cpu_set->next_cpu++;
356 }
357 
358 static struct bench_state {
359 	int res_cnt;
360 	struct bench_res *results;
361 	pthread_t *consumers;
362 	pthread_t *producers;
363 } state;
364 
365 const struct bench *bench = NULL;
366 
367 extern const struct bench bench_count_global;
368 extern const struct bench bench_count_local;
369 extern const struct bench bench_rename_base;
370 extern const struct bench bench_rename_kprobe;
371 extern const struct bench bench_rename_kretprobe;
372 extern const struct bench bench_rename_rawtp;
373 extern const struct bench bench_rename_fentry;
374 extern const struct bench bench_rename_fexit;
375 extern const struct bench bench_trig_base;
376 extern const struct bench bench_trig_tp;
377 extern const struct bench bench_trig_rawtp;
378 extern const struct bench bench_trig_kprobe;
379 extern const struct bench bench_trig_fentry;
380 extern const struct bench bench_trig_fentry_sleep;
381 extern const struct bench bench_trig_fmodret;
382 extern const struct bench bench_trig_uprobe_base;
383 extern const struct bench bench_trig_uprobe_with_nop;
384 extern const struct bench bench_trig_uretprobe_with_nop;
385 extern const struct bench bench_trig_uprobe_without_nop;
386 extern const struct bench bench_trig_uretprobe_without_nop;
387 extern const struct bench bench_rb_libbpf;
388 extern const struct bench bench_rb_custom;
389 extern const struct bench bench_pb_libbpf;
390 extern const struct bench bench_pb_custom;
391 extern const struct bench bench_bloom_lookup;
392 extern const struct bench bench_bloom_update;
393 extern const struct bench bench_bloom_false_positive;
394 extern const struct bench bench_hashmap_without_bloom;
395 extern const struct bench bench_hashmap_with_bloom;
396 extern const struct bench bench_bpf_loop;
397 extern const struct bench bench_strncmp_no_helper;
398 extern const struct bench bench_strncmp_helper;
399 
400 static const struct bench *benchs[] = {
401 	&bench_count_global,
402 	&bench_count_local,
403 	&bench_rename_base,
404 	&bench_rename_kprobe,
405 	&bench_rename_kretprobe,
406 	&bench_rename_rawtp,
407 	&bench_rename_fentry,
408 	&bench_rename_fexit,
409 	&bench_trig_base,
410 	&bench_trig_tp,
411 	&bench_trig_rawtp,
412 	&bench_trig_kprobe,
413 	&bench_trig_fentry,
414 	&bench_trig_fentry_sleep,
415 	&bench_trig_fmodret,
416 	&bench_trig_uprobe_base,
417 	&bench_trig_uprobe_with_nop,
418 	&bench_trig_uretprobe_with_nop,
419 	&bench_trig_uprobe_without_nop,
420 	&bench_trig_uretprobe_without_nop,
421 	&bench_rb_libbpf,
422 	&bench_rb_custom,
423 	&bench_pb_libbpf,
424 	&bench_pb_custom,
425 	&bench_bloom_lookup,
426 	&bench_bloom_update,
427 	&bench_bloom_false_positive,
428 	&bench_hashmap_without_bloom,
429 	&bench_hashmap_with_bloom,
430 	&bench_bpf_loop,
431 	&bench_strncmp_no_helper,
432 	&bench_strncmp_helper,
433 };
434 
435 static void setup_benchmark()
436 {
437 	int i, err;
438 
439 	if (!env.bench_name) {
440 		fprintf(stderr, "benchmark name is not specified\n");
441 		exit(1);
442 	}
443 
444 	for (i = 0; i < ARRAY_SIZE(benchs); i++) {
445 		if (strcmp(benchs[i]->name, env.bench_name) == 0) {
446 			bench = benchs[i];
447 			break;
448 		}
449 	}
450 	if (!bench) {
451 		fprintf(stderr, "benchmark '%s' not found\n", env.bench_name);
452 		exit(1);
453 	}
454 
455 	printf("Setting up benchmark '%s'...\n", bench->name);
456 
457 	state.producers = calloc(env.producer_cnt, sizeof(*state.producers));
458 	state.consumers = calloc(env.consumer_cnt, sizeof(*state.consumers));
459 	state.results = calloc(env.duration_sec + env.warmup_sec + 2,
460 			       sizeof(*state.results));
461 	if (!state.producers || !state.consumers || !state.results)
462 		exit(1);
463 
464 	if (bench->validate)
465 		bench->validate();
466 	if (bench->setup)
467 		bench->setup();
468 
469 	for (i = 0; i < env.consumer_cnt; i++) {
470 		err = pthread_create(&state.consumers[i], NULL,
471 				     bench->consumer_thread, (void *)(long)i);
472 		if (err) {
473 			fprintf(stderr, "failed to create consumer thread #%d: %d\n",
474 				i, -errno);
475 			exit(1);
476 		}
477 		if (env.affinity)
478 			set_thread_affinity(state.consumers[i],
479 					    next_cpu(&env.cons_cpus));
480 	}
481 
482 	/* unless explicit producer CPU list is specified, continue after
483 	 * last consumer CPU
484 	 */
485 	if (!env.prod_cpus.cpus)
486 		env.prod_cpus.next_cpu = env.cons_cpus.next_cpu;
487 
488 	for (i = 0; i < env.producer_cnt; i++) {
489 		err = pthread_create(&state.producers[i], NULL,
490 				     bench->producer_thread, (void *)(long)i);
491 		if (err) {
492 			fprintf(stderr, "failed to create producer thread #%d: %d\n",
493 				i, -errno);
494 			exit(1);
495 		}
496 		if (env.affinity)
497 			set_thread_affinity(state.producers[i],
498 					    next_cpu(&env.prod_cpus));
499 	}
500 
501 	printf("Benchmark '%s' started.\n", bench->name);
502 }
503 
504 static pthread_mutex_t bench_done_mtx = PTHREAD_MUTEX_INITIALIZER;
505 static pthread_cond_t bench_done = PTHREAD_COND_INITIALIZER;
506 
507 static void collect_measurements(long delta_ns) {
508 	int iter = state.res_cnt++;
509 	struct bench_res *res = &state.results[iter];
510 
511 	bench->measure(res);
512 
513 	if (bench->report_progress)
514 		bench->report_progress(iter, res, delta_ns);
515 
516 	if (iter == env.duration_sec + env.warmup_sec) {
517 		pthread_mutex_lock(&bench_done_mtx);
518 		pthread_cond_signal(&bench_done);
519 		pthread_mutex_unlock(&bench_done_mtx);
520 	}
521 }
522 
523 int main(int argc, char **argv)
524 {
525 	parse_cmdline_args(argc, argv);
526 
527 	if (env.list) {
528 		int i;
529 
530 		printf("Available benchmarks:\n");
531 		for (i = 0; i < ARRAY_SIZE(benchs); i++) {
532 			printf("- %s\n", benchs[i]->name);
533 		}
534 		return 0;
535 	}
536 
537 	setup_benchmark();
538 
539 	setup_timer();
540 
541 	pthread_mutex_lock(&bench_done_mtx);
542 	pthread_cond_wait(&bench_done, &bench_done_mtx);
543 	pthread_mutex_unlock(&bench_done_mtx);
544 
545 	if (bench->report_final)
546 		/* skip first sample */
547 		bench->report_final(state.results + env.warmup_sec,
548 				    state.res_cnt - env.warmup_sec);
549 
550 	return 0;
551 }
552