xref: /linux/tools/testing/selftests/bpf/benchs/bench_lpm_trie_map.c (revision 07fdad3a93756b872da7b53647715c48d0f4a2d0)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2025 Cloudflare */
3 
4 /*
5  * All of these benchmarks operate on tries with keys in the range
6  * [0, args.nr_entries), i.e. there are no gaps or partially filled
7  * branches of the trie for any key < args.nr_entries.
8  *
9  * This gives an idea of worst-case behaviour.
10  */
11 
12 #include <argp.h>
13 #include <linux/time64.h>
14 #include <linux/if_ether.h>
15 #include "lpm_trie_bench.skel.h"
16 #include "lpm_trie_map.skel.h"
17 #include "bench.h"
18 #include "testing_helpers.h"
19 #include "progs/lpm_trie.h"
20 
21 static struct ctx {
22 	struct lpm_trie_bench *bench;
23 } ctx;
24 
25 static struct {
26 	__u32 nr_entries;
27 	__u32 prefixlen;
28 	bool random;
29 } args = {
30 	.nr_entries = 0,
31 	.prefixlen = 32,
32 	.random = false,
33 };
34 
35 enum {
36 	ARG_NR_ENTRIES = 9000,
37 	ARG_PREFIX_LEN,
38 	ARG_RANDOM,
39 };
40 
41 static const struct argp_option opts[] = {
42 	{ "nr_entries", ARG_NR_ENTRIES, "NR_ENTRIES", 0,
43 	  "Number of unique entries in the LPM trie" },
44 	{ "prefix_len", ARG_PREFIX_LEN, "PREFIX_LEN", 0,
45 	  "Number of prefix bits to use in the LPM trie" },
46 	{ "random", ARG_RANDOM, NULL, 0, "Access random keys during op" },
47 	{},
48 };
49 
50 static error_t lpm_parse_arg(int key, char *arg, struct argp_state *state)
51 {
52 	long ret;
53 
54 	switch (key) {
55 	case ARG_NR_ENTRIES:
56 		ret = strtol(arg, NULL, 10);
57 		if (ret < 1 || ret > UINT_MAX) {
58 			fprintf(stderr, "Invalid nr_entries count.");
59 			argp_usage(state);
60 		}
61 		args.nr_entries = ret;
62 		break;
63 	case ARG_PREFIX_LEN:
64 		ret = strtol(arg, NULL, 10);
65 		if (ret < 1 || ret > UINT_MAX) {
66 			fprintf(stderr, "Invalid prefix_len value.");
67 			argp_usage(state);
68 		}
69 		args.prefixlen = ret;
70 		break;
71 	case ARG_RANDOM:
72 		args.random = true;
73 		break;
74 	default:
75 		return ARGP_ERR_UNKNOWN;
76 	}
77 	return 0;
78 }
79 
80 const struct argp bench_lpm_trie_map_argp = {
81 	.options = opts,
82 	.parser = lpm_parse_arg,
83 };
84 
85 static void validate_common(void)
86 {
87 	if (env.consumer_cnt != 0) {
88 		fprintf(stderr, "benchmark doesn't support consumer\n");
89 		exit(1);
90 	}
91 
92 	if (args.nr_entries == 0) {
93 		fprintf(stderr, "Missing --nr_entries parameter\n");
94 		exit(1);
95 	}
96 
97 	if ((1UL << args.prefixlen) < args.nr_entries) {
98 		fprintf(stderr, "prefix_len value too small for nr_entries\n");
99 		exit(1);
100 	}
101 }
102 
103 static void lpm_insert_validate(void)
104 {
105 	validate_common();
106 
107 	if (env.producer_cnt != 1) {
108 		fprintf(stderr, "lpm-trie-insert requires a single producer\n");
109 		exit(1);
110 	}
111 
112 	if (args.random) {
113 		fprintf(stderr, "lpm-trie-insert does not support --random\n");
114 		exit(1);
115 	}
116 }
117 
118 static void lpm_delete_validate(void)
119 {
120 	validate_common();
121 
122 	if (env.producer_cnt != 1) {
123 		fprintf(stderr, "lpm-trie-delete requires a single producer\n");
124 		exit(1);
125 	}
126 
127 	if (args.random) {
128 		fprintf(stderr, "lpm-trie-delete does not support --random\n");
129 		exit(1);
130 	}
131 }
132 
133 static void lpm_free_validate(void)
134 {
135 	validate_common();
136 
137 	if (env.producer_cnt != 1) {
138 		fprintf(stderr, "lpm-trie-free requires a single producer\n");
139 		exit(1);
140 	}
141 
142 	if (args.random) {
143 		fprintf(stderr, "lpm-trie-free does not support --random\n");
144 		exit(1);
145 	}
146 }
147 
148 static struct trie_key *keys;
149 static __u32 *vals;
150 
151 static void fill_map(int map_fd)
152 {
153 	int err;
154 
155 	DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
156 		.elem_flags = 0,
157 		.flags = 0,
158 	);
159 
160 	err = bpf_map_update_batch(map_fd, keys, vals, &args.nr_entries, &opts);
161 	if (err) {
162 		fprintf(stderr, "failed to batch update keys to map: %d\n",
163 			-err);
164 		exit(1);
165 	}
166 }
167 
168 static void empty_map(int map_fd)
169 {
170 	int err;
171 
172 	DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
173 		.elem_flags = 0,
174 		.flags = 0,
175 	);
176 
177 	err = bpf_map_delete_batch(map_fd, keys, &args.nr_entries, &opts);
178 	if (err) {
179 		fprintf(stderr, "failed to batch delete keys for map: %d\n",
180 			-err);
181 		exit(1);
182 	}
183 }
184 
185 static void attach_prog(void)
186 {
187 	int i;
188 
189 	ctx.bench = lpm_trie_bench__open_and_load();
190 	if (!ctx.bench) {
191 		fprintf(stderr, "failed to open skeleton\n");
192 		exit(1);
193 	}
194 
195 	ctx.bench->bss->nr_entries = args.nr_entries;
196 	ctx.bench->bss->prefixlen = args.prefixlen;
197 	ctx.bench->bss->random = args.random;
198 
199 	if (lpm_trie_bench__attach(ctx.bench)) {
200 		fprintf(stderr, "failed to attach skeleton\n");
201 		exit(1);
202 	}
203 
204 	keys = calloc(args.nr_entries, sizeof(*keys));
205 	vals = calloc(args.nr_entries, sizeof(*vals));
206 
207 	for (i = 0; i < args.nr_entries; i++) {
208 		struct trie_key *k = &keys[i];
209 		__u32 *v = &vals[i];
210 
211 		k->prefixlen = args.prefixlen;
212 		k->data = i;
213 		*v = 1;
214 	}
215 }
216 
217 static void attach_prog_and_fill_map(void)
218 {
219 	int fd;
220 
221 	attach_prog();
222 
223 	fd = bpf_map__fd(ctx.bench->maps.trie_map);
224 	fill_map(fd);
225 }
226 
227 static void lpm_noop_setup(void)
228 {
229 	attach_prog();
230 	ctx.bench->bss->op = LPM_OP_NOOP;
231 }
232 
233 static void lpm_baseline_setup(void)
234 {
235 	attach_prog();
236 	ctx.bench->bss->op = LPM_OP_BASELINE;
237 }
238 
239 static void lpm_lookup_setup(void)
240 {
241 	attach_prog_and_fill_map();
242 	ctx.bench->bss->op = LPM_OP_LOOKUP;
243 }
244 
245 static void lpm_insert_setup(void)
246 {
247 	attach_prog();
248 	ctx.bench->bss->op = LPM_OP_INSERT;
249 }
250 
251 static void lpm_update_setup(void)
252 {
253 	attach_prog_and_fill_map();
254 	ctx.bench->bss->op = LPM_OP_UPDATE;
255 }
256 
257 static void lpm_delete_setup(void)
258 {
259 	attach_prog_and_fill_map();
260 	ctx.bench->bss->op = LPM_OP_DELETE;
261 }
262 
263 static void lpm_free_setup(void)
264 {
265 	attach_prog();
266 	ctx.bench->bss->op = LPM_OP_FREE;
267 }
268 
269 static void lpm_measure(struct bench_res *res)
270 {
271 	res->hits = atomic_swap(&ctx.bench->bss->hits, 0);
272 	res->duration_ns = atomic_swap(&ctx.bench->bss->duration_ns, 0);
273 }
274 
275 static void bench_reinit_map(void)
276 {
277 	int fd = bpf_map__fd(ctx.bench->maps.trie_map);
278 
279 	switch (ctx.bench->bss->op) {
280 	case LPM_OP_INSERT:
281 		/* trie_map needs to be emptied */
282 		empty_map(fd);
283 		break;
284 	case LPM_OP_DELETE:
285 		/* trie_map needs to be refilled */
286 		fill_map(fd);
287 		break;
288 	default:
289 		fprintf(stderr, "Unexpected REINIT return code for op %d\n",
290 				ctx.bench->bss->op);
291 		exit(1);
292 	}
293 }
294 
295 /* For NOOP, BASELINE, LOOKUP, INSERT, UPDATE, and DELETE */
296 static void *lpm_producer(void *unused __always_unused)
297 {
298 	int err;
299 	char in[ETH_HLEN]; /* unused */
300 
301 	LIBBPF_OPTS(bpf_test_run_opts, opts, .data_in = in,
302 		    .data_size_in = sizeof(in), .repeat = 1, );
303 
304 	while (true) {
305 		int fd = bpf_program__fd(ctx.bench->progs.run_bench);
306 		err = bpf_prog_test_run_opts(fd, &opts);
307 		if (err) {
308 			fprintf(stderr, "failed to run BPF prog: %d\n", err);
309 			exit(1);
310 		}
311 
312 		/* Check for kernel error code */
313 		if ((int)opts.retval < 0) {
314 			fprintf(stderr, "BPF prog returned error: %d\n",
315 				opts.retval);
316 			exit(1);
317 		}
318 
319 		switch (opts.retval) {
320 		case LPM_BENCH_SUCCESS:
321 			break;
322 		case LPM_BENCH_REINIT_MAP:
323 			bench_reinit_map();
324 			break;
325 		default:
326 			fprintf(stderr, "Unexpected BPF prog return code %d for op %d\n",
327 					opts.retval, ctx.bench->bss->op);
328 			exit(1);
329 		}
330 	}
331 
332 	return NULL;
333 }
334 
335 static void *lpm_free_producer(void *unused __always_unused)
336 {
337 	while (true) {
338 		struct lpm_trie_map *skel;
339 
340 		skel = lpm_trie_map__open_and_load();
341 		if (!skel) {
342 			fprintf(stderr, "failed to open skeleton\n");
343 			exit(1);
344 		}
345 
346 		fill_map(bpf_map__fd(skel->maps.trie_free_map));
347 		lpm_trie_map__destroy(skel);
348 	}
349 
350 	return NULL;
351 }
352 
353 /*
354  * The standard bench op_report_*() functions assume measurements are
355  * taken over a 1-second interval but operations that modify the map
356  * (INSERT, DELETE, and FREE) cannot run indefinitely without
357  * "resetting" the map to the initial state. Depending on the size of
358  * the map, this likely needs to happen before the 1-second timer fires.
359  *
360  * Calculate the fraction of a second over which the op measurement was
361  * taken (to ignore any time spent doing the reset) and report the
362  * throughput results per second.
363  */
364 static void frac_second_report_progress(int iter, struct bench_res *res,
365 					long delta_ns, double rate_divisor,
366 					char rate)
367 {
368 	double hits_per_sec, hits_per_prod;
369 
370 	hits_per_sec = res->hits / rate_divisor /
371 		(res->duration_ns / (double)NSEC_PER_SEC);
372 	hits_per_prod = hits_per_sec / env.producer_cnt;
373 
374 	printf("Iter %3d (%7.3lfus): ", iter,
375 	       (delta_ns - NSEC_PER_SEC) / 1000.0);
376 	printf("hits %8.3lf%c/s (%7.3lf%c/prod)\n", hits_per_sec, rate,
377 	       hits_per_prod, rate);
378 }
379 
380 static void frac_second_report_final(struct bench_res res[], int res_cnt,
381 				     double lat_divisor, double rate_divisor,
382 				     char rate, const char *unit)
383 {
384 	double hits_mean = 0.0, hits_stddev = 0.0;
385 	double latency = 0.0;
386 	int i;
387 
388 	for (i = 0; i < res_cnt; i++) {
389 		double val = res[i].hits / rate_divisor /
390 			     (res[i].duration_ns / (double)NSEC_PER_SEC);
391 		hits_mean += val / (0.0 + res_cnt);
392 		latency += res[i].duration_ns / res[i].hits / (0.0 + res_cnt);
393 	}
394 
395 	if (res_cnt > 1) {
396 		for (i = 0; i < res_cnt; i++) {
397 			double val =
398 				res[i].hits / rate_divisor /
399 				(res[i].duration_ns / (double)NSEC_PER_SEC);
400 			hits_stddev += (hits_mean - val) * (hits_mean - val) /
401 				       (res_cnt - 1.0);
402 		}
403 
404 		hits_stddev = sqrt(hits_stddev);
405 	}
406 	printf("Summary: throughput %8.3lf \u00B1 %5.3lf %c ops/s (%7.3lf%c ops/prod), ",
407 	       hits_mean, hits_stddev, rate, hits_mean / env.producer_cnt,
408 	       rate);
409 	printf("latency %8.3lf %s/op\n",
410 	       latency / lat_divisor / env.producer_cnt, unit);
411 }
412 
413 static void insert_ops_report_progress(int iter, struct bench_res *res,
414 				       long delta_ns)
415 {
416 	double rate_divisor = 1000000.0;
417 	char rate = 'M';
418 
419 	frac_second_report_progress(iter, res, delta_ns, rate_divisor, rate);
420 }
421 
422 static void delete_ops_report_progress(int iter, struct bench_res *res,
423 				       long delta_ns)
424 {
425 	double rate_divisor = 1000000.0;
426 	char rate = 'M';
427 
428 	frac_second_report_progress(iter, res, delta_ns, rate_divisor, rate);
429 }
430 
431 static void free_ops_report_progress(int iter, struct bench_res *res,
432 				     long delta_ns)
433 {
434 	double rate_divisor = 1000.0;
435 	char rate = 'K';
436 
437 	frac_second_report_progress(iter, res, delta_ns, rate_divisor, rate);
438 }
439 
440 static void insert_ops_report_final(struct bench_res res[], int res_cnt)
441 {
442 	double lat_divisor = 1.0;
443 	double rate_divisor = 1000000.0;
444 	const char *unit = "ns";
445 	char rate = 'M';
446 
447 	frac_second_report_final(res, res_cnt, lat_divisor, rate_divisor, rate,
448 				 unit);
449 }
450 
451 static void delete_ops_report_final(struct bench_res res[], int res_cnt)
452 {
453 	double lat_divisor = 1.0;
454 	double rate_divisor = 1000000.0;
455 	const char *unit = "ns";
456 	char rate = 'M';
457 
458 	frac_second_report_final(res, res_cnt, lat_divisor, rate_divisor, rate,
459 				 unit);
460 }
461 
462 static void free_ops_report_final(struct bench_res res[], int res_cnt)
463 {
464 	double lat_divisor = 1000000.0;
465 	double rate_divisor = 1000.0;
466 	const char *unit = "ms";
467 	char rate = 'K';
468 
469 	frac_second_report_final(res, res_cnt, lat_divisor, rate_divisor, rate,
470 				 unit);
471 }
472 
473 /* noop bench measures harness-overhead */
474 const struct bench bench_lpm_trie_noop = {
475 	.name = "lpm-trie-noop",
476 	.argp = &bench_lpm_trie_map_argp,
477 	.validate = validate_common,
478 	.setup = lpm_noop_setup,
479 	.producer_thread = lpm_producer,
480 	.measure = lpm_measure,
481 	.report_progress = ops_report_progress,
482 	.report_final = ops_report_final,
483 };
484 
485 /* baseline overhead for lookup and update */
486 const struct bench bench_lpm_trie_baseline = {
487 	.name = "lpm-trie-baseline",
488 	.argp = &bench_lpm_trie_map_argp,
489 	.validate = validate_common,
490 	.setup = lpm_baseline_setup,
491 	.producer_thread = lpm_producer,
492 	.measure = lpm_measure,
493 	.report_progress = ops_report_progress,
494 	.report_final = ops_report_final,
495 };
496 
497 /* measure cost of doing a lookup on existing entries in a full trie */
498 const struct bench bench_lpm_trie_lookup = {
499 	.name = "lpm-trie-lookup",
500 	.argp = &bench_lpm_trie_map_argp,
501 	.validate = validate_common,
502 	.setup = lpm_lookup_setup,
503 	.producer_thread = lpm_producer,
504 	.measure = lpm_measure,
505 	.report_progress = ops_report_progress,
506 	.report_final = ops_report_final,
507 };
508 
509 /* measure cost of inserting new entries into an empty trie */
510 const struct bench bench_lpm_trie_insert = {
511 	.name = "lpm-trie-insert",
512 	.argp = &bench_lpm_trie_map_argp,
513 	.validate = lpm_insert_validate,
514 	.setup = lpm_insert_setup,
515 	.producer_thread = lpm_producer,
516 	.measure = lpm_measure,
517 	.report_progress = insert_ops_report_progress,
518 	.report_final = insert_ops_report_final,
519 };
520 
521 /* measure cost of updating existing entries in a full trie */
522 const struct bench bench_lpm_trie_update = {
523 	.name = "lpm-trie-update",
524 	.argp = &bench_lpm_trie_map_argp,
525 	.validate = validate_common,
526 	.setup = lpm_update_setup,
527 	.producer_thread = lpm_producer,
528 	.measure = lpm_measure,
529 	.report_progress = ops_report_progress,
530 	.report_final = ops_report_final,
531 };
532 
533 /* measure cost of deleting existing entries from a full trie */
534 const struct bench bench_lpm_trie_delete = {
535 	.name = "lpm-trie-delete",
536 	.argp = &bench_lpm_trie_map_argp,
537 	.validate = lpm_delete_validate,
538 	.setup = lpm_delete_setup,
539 	.producer_thread = lpm_producer,
540 	.measure = lpm_measure,
541 	.report_progress = delete_ops_report_progress,
542 	.report_final = delete_ops_report_final,
543 };
544 
545 /* measure cost of freeing a full trie */
546 const struct bench bench_lpm_trie_free = {
547 	.name = "lpm-trie-free",
548 	.argp = &bench_lpm_trie_map_argp,
549 	.validate = lpm_free_validate,
550 	.setup = lpm_free_setup,
551 	.producer_thread = lpm_free_producer,
552 	.measure = lpm_measure,
553 	.report_progress = free_ops_report_progress,
554 	.report_final = free_ops_report_final,
555 };
556