xref: /linux/tools/testing/selftests/bpf/benchs/bench_bpf_hashmap_lookup.c (revision 84f7a49e76ec8e0a1e18f3758e89800f8cf8cfc6)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2023 Isovalent */
3 
4 #include <sys/random.h>
5 #include <argp.h>
6 #include "bench.h"
7 #include "bpf_hashmap_lookup.skel.h"
8 #include "bpf_util.h"
9 
10 /* BPF triggering benchmarks */
11 static struct ctx {
12 	struct bpf_hashmap_lookup *skel;
13 } ctx;
14 
15 /* only available to kernel, so define it here */
16 #define BPF_MAX_LOOPS (1<<23)
17 
18 #define MAX_KEY_SIZE 1024 /* the size of the key map */
19 
20 static struct {
21 	__u32 key_size;
22 	__u32 map_flags;
23 	__u32 max_entries;
24 	__u32 nr_entries;
25 	__u32 nr_loops;
26 } args = {
27 	.key_size = 4,
28 	.map_flags = 0,
29 	.max_entries = 1000,
30 	.nr_entries = 500,
31 	.nr_loops = 1000000,
32 };
33 
34 enum {
35 	ARG_KEY_SIZE = 8001,
36 	ARG_MAP_FLAGS,
37 	ARG_MAX_ENTRIES,
38 	ARG_NR_ENTRIES,
39 	ARG_NR_LOOPS,
40 };
41 
42 static const struct argp_option opts[] = {
43 	{ "key_size", ARG_KEY_SIZE, "KEY_SIZE", 0,
44 	  "The hashmap key size (max 1024)"},
45 	{ "map_flags", ARG_MAP_FLAGS, "MAP_FLAGS", 0,
46 	  "The hashmap flags passed to BPF_MAP_CREATE"},
47 	{ "max_entries", ARG_MAX_ENTRIES, "MAX_ENTRIES", 0,
48 	  "The hashmap max entries"},
49 	{ "nr_entries", ARG_NR_ENTRIES, "NR_ENTRIES", 0,
50 	  "The number of entries to insert/lookup"},
51 	{ "nr_loops", ARG_NR_LOOPS, "NR_LOOPS", 0,
52 	  "The number of loops for the benchmark"},
53 	{},
54 };
55 
56 static error_t parse_arg(int key, char *arg, struct argp_state *state)
57 {
58 	long ret;
59 
60 	switch (key) {
61 	case ARG_KEY_SIZE:
62 		ret = strtol(arg, NULL, 10);
63 		if (ret < 1 || ret > MAX_KEY_SIZE) {
64 			fprintf(stderr, "invalid key_size");
65 			argp_usage(state);
66 		}
67 		args.key_size = ret;
68 		break;
69 	case ARG_MAP_FLAGS:
70 		ret = strtol(arg, NULL, 0);
71 		if (ret < 0 || ret > UINT_MAX) {
72 			fprintf(stderr, "invalid map_flags");
73 			argp_usage(state);
74 		}
75 		args.map_flags = ret;
76 		break;
77 	case ARG_MAX_ENTRIES:
78 		ret = strtol(arg, NULL, 10);
79 		if (ret < 1 || ret > UINT_MAX) {
80 			fprintf(stderr, "invalid max_entries");
81 			argp_usage(state);
82 		}
83 		args.max_entries = ret;
84 		break;
85 	case ARG_NR_ENTRIES:
86 		ret = strtol(arg, NULL, 10);
87 		if (ret < 1 || ret > UINT_MAX) {
88 			fprintf(stderr, "invalid nr_entries");
89 			argp_usage(state);
90 		}
91 		args.nr_entries = ret;
92 		break;
93 	case ARG_NR_LOOPS:
94 		ret = strtol(arg, NULL, 10);
95 		if (ret < 1 || ret > BPF_MAX_LOOPS) {
96 			fprintf(stderr, "invalid nr_loops: %ld (min=1 max=%u)\n",
97 				ret, BPF_MAX_LOOPS);
98 			argp_usage(state);
99 		}
100 		args.nr_loops = ret;
101 		break;
102 	default:
103 		return ARGP_ERR_UNKNOWN;
104 	}
105 
106 	return 0;
107 }
108 
109 const struct argp bench_hashmap_lookup_argp = {
110 	.options = opts,
111 	.parser = parse_arg,
112 };
113 
114 static void validate(void)
115 {
116 	if (env.consumer_cnt != 0) {
117 		fprintf(stderr, "benchmark doesn't support consumer!\n");
118 		exit(1);
119 	}
120 
121 	if (args.nr_entries > args.max_entries) {
122 		fprintf(stderr, "args.nr_entries is too big! (max %u, got %u)\n",
123 			args.max_entries, args.nr_entries);
124 		exit(1);
125 	}
126 }
127 
128 static void *producer(void *input)
129 {
130 	while (true) {
131 		/* trigger the bpf program */
132 		syscall(__NR_getpgid);
133 	}
134 	return NULL;
135 }
136 
137 static void measure(struct bench_res *res)
138 {
139 }
140 
141 static inline void patch_key(u32 i, u32 *key)
142 {
143 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
144 	*key = i + 1;
145 #else
146 	*key = __builtin_bswap32(i + 1);
147 #endif
148 	/* the rest of key is random */
149 }
150 
151 static void hashmap_lookup_setup(enum bpf_map_type map_type)
152 {
153 	struct bpf_link *link;
154 	__u32 map_flags;
155 	int map_fd;
156 	int ret;
157 	int i;
158 
159 	setup_libbpf();
160 
161 	ctx.skel = bpf_hashmap_lookup__open();
162 	if (!ctx.skel) {
163 		fprintf(stderr, "failed to open skeleton\n");
164 		exit(1);
165 	}
166 
167 	map_flags = args.map_flags;
168 	if (map_type == BPF_MAP_TYPE_RHASH)
169 		map_flags |= BPF_F_NO_PREALLOC;
170 
171 	bpf_map__set_type(ctx.skel->maps.hash_map_bench, map_type);
172 	bpf_map__set_max_entries(ctx.skel->maps.hash_map_bench, args.max_entries);
173 	bpf_map__set_key_size(ctx.skel->maps.hash_map_bench, args.key_size);
174 	bpf_map__set_value_size(ctx.skel->maps.hash_map_bench, 8);
175 	bpf_map__set_map_flags(ctx.skel->maps.hash_map_bench, map_flags);
176 
177 	ctx.skel->bss->nr_entries = args.nr_entries;
178 	ctx.skel->bss->nr_loops = args.nr_loops / args.nr_entries;
179 
180 	if (args.key_size > 4) {
181 		for (i = 1; i < args.key_size/4; i++)
182 			ctx.skel->bss->key[i] = 2654435761 * i;
183 	}
184 
185 	ret = bpf_hashmap_lookup__load(ctx.skel);
186 	if (ret) {
187 		bpf_hashmap_lookup__destroy(ctx.skel);
188 		fprintf(stderr, "failed to load map: %s", strerror(-ret));
189 		exit(1);
190 	}
191 
192 	/* fill in the hash_map */
193 	map_fd = bpf_map__fd(ctx.skel->maps.hash_map_bench);
194 	for (u64 i = 0; i < args.nr_entries; i++) {
195 		patch_key(i, ctx.skel->bss->key);
196 		bpf_map_update_elem(map_fd, ctx.skel->bss->key, &i, BPF_ANY);
197 	}
198 
199 	link = bpf_program__attach(ctx.skel->progs.benchmark);
200 	if (!link) {
201 		fprintf(stderr, "failed to attach program!\n");
202 		exit(1);
203 	}
204 }
205 
206 static void setup(void)
207 {
208 	hashmap_lookup_setup(BPF_MAP_TYPE_HASH);
209 }
210 
211 static void rhash_setup(void)
212 {
213 	hashmap_lookup_setup(BPF_MAP_TYPE_RHASH);
214 }
215 
216 static inline double events_from_time(u64 time)
217 {
218 	if (time)
219 		return args.nr_loops * 1000000000llu / time / 1000000.0L;
220 
221 	return 0;
222 }
223 
224 static int compute_events(u64 *times, double *events_mean, double *events_stddev, u64 *mean_time)
225 {
226 	int i, n = 0;
227 
228 	*events_mean = 0;
229 	*events_stddev = 0;
230 	*mean_time = 0;
231 
232 	for (i = 0; i < 32; i++) {
233 		if (!times[i])
234 			break;
235 		*mean_time += times[i];
236 		*events_mean += events_from_time(times[i]);
237 		n += 1;
238 	}
239 	if (!n)
240 		return 0;
241 
242 	*mean_time /= n;
243 	*events_mean /= n;
244 
245 	if (n > 1) {
246 		for (i = 0; i < n; i++) {
247 			double events_i = *events_mean - events_from_time(times[i]);
248 			*events_stddev += events_i * events_i / (n - 1);
249 		}
250 		*events_stddev = sqrt(*events_stddev);
251 	}
252 
253 	return n;
254 }
255 
256 static void hashmap_report_final(struct bench_res res[], int res_cnt)
257 {
258 	unsigned int nr_cpus = bpf_num_possible_cpus();
259 	double events_mean, events_stddev;
260 	u64 mean_time;
261 	int i, n;
262 
263 	for (i = 0; i < nr_cpus; i++) {
264 		n = compute_events(ctx.skel->bss->percpu_times[i], &events_mean,
265 				   &events_stddev, &mean_time);
266 		if (n == 0)
267 			continue;
268 
269 		if (env.quiet) {
270 			/* we expect only one cpu to be present */
271 			if (env.affinity)
272 				printf("%.3lf\n", events_mean);
273 			else
274 				printf("cpu%02d %.3lf\n", i, events_mean);
275 		} else {
276 			printf("cpu%02d: lookup %.3lfM ± %.3lfM events/sec"
277 			       " (approximated from %d samples of ~%lums)\n",
278 			       i, events_mean, 2*events_stddev,
279 			       n, mean_time / 1000000);
280 		}
281 	}
282 }
283 
284 const struct bench bench_bpf_hashmap_lookup = {
285 	.name = "bpf-hashmap-lookup",
286 	.argp = &bench_hashmap_lookup_argp,
287 	.validate = validate,
288 	.setup = setup,
289 	.producer_thread = producer,
290 	.measure = measure,
291 	.report_progress = NULL,
292 	.report_final = hashmap_report_final,
293 };
294 
295 const struct bench bench_bpf_rhashmap_lookup = {
296 	.name = "bpf-rhashmap-lookup",
297 	.argp = &bench_hashmap_lookup_argp,
298 	.validate = validate,
299 	.setup = rhash_setup,
300 	.producer_thread = producer,
301 	.measure = measure,
302 	.report_progress = NULL,
303 	.report_final = hashmap_report_final,
304 };
305