|
| 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 | + |
| 20 | +static struct ctx { |
| 21 | + struct lpm_trie_bench *bench; |
| 22 | +} ctx; |
| 23 | + |
| 24 | +static struct { |
| 25 | + __u32 nr_entries; |
| 26 | + __u32 prefixlen; |
| 27 | +} args = { |
| 28 | + .nr_entries = 10000, |
| 29 | + .prefixlen = 32, |
| 30 | +}; |
| 31 | + |
| 32 | +enum { |
| 33 | + ARG_NR_ENTRIES = 9000, |
| 34 | + ARG_PREFIX_LEN, |
| 35 | +}; |
| 36 | + |
| 37 | +static const struct argp_option opts[] = { |
| 38 | + { "nr_entries", ARG_NR_ENTRIES, "NR_ENTRIES", 0, |
| 39 | + "Number of unique entries in the LPM trie" }, |
| 40 | + { "prefix_len", ARG_PREFIX_LEN, "PREFIX_LEN", 0, |
| 41 | + "Number of prefix bits to use in the LPM trie" }, |
| 42 | + {}, |
| 43 | +}; |
| 44 | + |
| 45 | +static error_t lpm_parse_arg(int key, char *arg, struct argp_state *state) |
| 46 | +{ |
| 47 | + long ret; |
| 48 | + |
| 49 | + switch (key) { |
| 50 | + case ARG_NR_ENTRIES: |
| 51 | + ret = strtol(arg, NULL, 10); |
| 52 | + if (ret < 1 || ret > UINT_MAX) { |
| 53 | + fprintf(stderr, "Invalid nr_entries count."); |
| 54 | + argp_usage(state); |
| 55 | + } |
| 56 | + args.nr_entries = ret; |
| 57 | + break; |
| 58 | + case ARG_PREFIX_LEN: |
| 59 | + ret = strtol(arg, NULL, 10); |
| 60 | + if (ret < 1 || ret > UINT_MAX) { |
| 61 | + fprintf(stderr, "Invalid prefix_len value."); |
| 62 | + argp_usage(state); |
| 63 | + } |
| 64 | + args.prefixlen = ret; |
| 65 | + break; |
| 66 | + default: |
| 67 | + return ARGP_ERR_UNKNOWN; |
| 68 | + } |
| 69 | + return 0; |
| 70 | +} |
| 71 | + |
| 72 | +const struct argp bench_lpm_trie_map_argp = { |
| 73 | + .options = opts, |
| 74 | + .parser = lpm_parse_arg, |
| 75 | +}; |
| 76 | + |
| 77 | +static void __lpm_validate(void) |
| 78 | +{ |
| 79 | + if (env.consumer_cnt != 0) { |
| 80 | + fprintf(stderr, "benchmark doesn't support consumer!\n"); |
| 81 | + exit(1); |
| 82 | + } |
| 83 | + |
| 84 | + if ((1UL << args.prefixlen) < args.nr_entries) { |
| 85 | + fprintf(stderr, "prefix_len value too small for nr_entries!\n"); |
| 86 | + exit(1); |
| 87 | + }; |
| 88 | +} |
| 89 | + |
| 90 | +enum { OP_LOOKUP = 1, OP_UPDATE, OP_DELETE, OP_FREE }; |
| 91 | + |
| 92 | +static void lpm_delete_validate(void) |
| 93 | +{ |
| 94 | + __lpm_validate(); |
| 95 | + |
| 96 | + if (env.producer_cnt != 1) { |
| 97 | + fprintf(stderr, |
| 98 | + "lpm-trie-delete requires a single producer!\n"); |
| 99 | + exit(1); |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +static void lpm_free_validate(void) |
| 104 | +{ |
| 105 | + __lpm_validate(); |
| 106 | + |
| 107 | + if (env.producer_cnt != 1) { |
| 108 | + fprintf(stderr, "lpm-trie-free requires a single producer!\n"); |
| 109 | + exit(1); |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +static void fill_map(int map_fd) |
| 114 | +{ |
| 115 | + int i, err; |
| 116 | + |
| 117 | + for (i = 0; i < args.nr_entries; i++) { |
| 118 | + struct trie_key { |
| 119 | + __u32 prefixlen; |
| 120 | + __u32 data; |
| 121 | + } key = { args.prefixlen, i }; |
| 122 | + __u32 val = 1; |
| 123 | + |
| 124 | + err = bpf_map_update_elem(map_fd, &key, &val, BPF_NOEXIST); |
| 125 | + if (err) { |
| 126 | + fprintf(stderr, "failed to add key %d to map: %d\n", |
| 127 | + key.data, -err); |
| 128 | + exit(1); |
| 129 | + } |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +static void __lpm_setup(void) |
| 134 | +{ |
| 135 | + ctx.bench = lpm_trie_bench__open_and_load(); |
| 136 | + if (!ctx.bench) { |
| 137 | + fprintf(stderr, "failed to open skeleton\n"); |
| 138 | + exit(1); |
| 139 | + } |
| 140 | + |
| 141 | + ctx.bench->bss->nr_entries = args.nr_entries; |
| 142 | + ctx.bench->bss->prefixlen = args.prefixlen; |
| 143 | + |
| 144 | + if (lpm_trie_bench__attach(ctx.bench)) { |
| 145 | + fprintf(stderr, "failed to attach skeleton\n"); |
| 146 | + exit(1); |
| 147 | + } |
| 148 | +} |
| 149 | + |
| 150 | +static void lpm_setup(void) |
| 151 | +{ |
| 152 | + int fd; |
| 153 | + |
| 154 | + __lpm_setup(); |
| 155 | + |
| 156 | + fd = bpf_map__fd(ctx.bench->maps.trie_map); |
| 157 | + fill_map(fd); |
| 158 | +} |
| 159 | + |
| 160 | +static void lpm_lookup_setup(void) |
| 161 | +{ |
| 162 | + lpm_setup(); |
| 163 | + |
| 164 | + ctx.bench->bss->op = OP_LOOKUP; |
| 165 | +} |
| 166 | + |
| 167 | +static void lpm_update_setup(void) |
| 168 | +{ |
| 169 | + lpm_setup(); |
| 170 | + |
| 171 | + ctx.bench->bss->op = OP_UPDATE; |
| 172 | +} |
| 173 | + |
| 174 | +static void lpm_delete_setup(void) |
| 175 | +{ |
| 176 | + lpm_setup(); |
| 177 | + |
| 178 | + ctx.bench->bss->op = OP_DELETE; |
| 179 | +} |
| 180 | + |
| 181 | +static void lpm_free_setup(void) |
| 182 | +{ |
| 183 | + __lpm_setup(); |
| 184 | + ctx.bench->bss->op = OP_FREE; |
| 185 | +} |
| 186 | + |
| 187 | +static void lpm_measure(struct bench_res *res) |
| 188 | +{ |
| 189 | + res->hits = atomic_swap(&ctx.bench->bss->hits, 0); |
| 190 | + res->duration_ns = atomic_swap(&ctx.bench->bss->duration_ns, 0); |
| 191 | +} |
| 192 | + |
| 193 | +/* For LOOKUP, UPDATE, and DELETE */ |
| 194 | +static void *lpm_producer(void *unused __always_unused) |
| 195 | +{ |
| 196 | + int err; |
| 197 | + char in[ETH_HLEN]; /* unused */ |
| 198 | + |
| 199 | + LIBBPF_OPTS(bpf_test_run_opts, opts, .data_in = in, |
| 200 | + .data_size_in = sizeof(in), .repeat = 1, ); |
| 201 | + |
| 202 | + while (true) { |
| 203 | + int fd = bpf_program__fd(ctx.bench->progs.run_bench); |
| 204 | + err = bpf_prog_test_run_opts(fd, &opts); |
| 205 | + if (err) { |
| 206 | + fprintf(stderr, "failed to run BPF prog: %d\n", err); |
| 207 | + exit(1); |
| 208 | + } |
| 209 | + |
| 210 | + if (opts.retval < 0) { |
| 211 | + fprintf(stderr, "BPF prog returned error: %d\n", |
| 212 | + opts.retval); |
| 213 | + exit(1); |
| 214 | + } |
| 215 | + |
| 216 | + if (ctx.bench->bss->op == OP_DELETE && opts.retval == 1) { |
| 217 | + /* trie_map needs to be refilled */ |
| 218 | + fill_map(bpf_map__fd(ctx.bench->maps.trie_map)); |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + return NULL; |
| 223 | +} |
| 224 | + |
| 225 | +static void *lpm_free_producer(void *unused __always_unused) |
| 226 | +{ |
| 227 | + while (true) { |
| 228 | + struct lpm_trie_map *skel; |
| 229 | + |
| 230 | + skel = lpm_trie_map__open_and_load(); |
| 231 | + if (!skel) { |
| 232 | + fprintf(stderr, "failed to open skeleton\n"); |
| 233 | + exit(1); |
| 234 | + } |
| 235 | + |
| 236 | + fill_map(bpf_map__fd(skel->maps.trie_free_map)); |
| 237 | + lpm_trie_map__destroy(skel); |
| 238 | + } |
| 239 | + |
| 240 | + return NULL; |
| 241 | +} |
| 242 | + |
| 243 | +static void free_ops_report_progress(int iter, struct bench_res *res, |
| 244 | + long delta_ns) |
| 245 | +{ |
| 246 | + double hits_per_sec, hits_per_prod; |
| 247 | + double rate_divisor = 1000.0; |
| 248 | + char rate = 'K'; |
| 249 | + |
| 250 | + hits_per_sec = res->hits / (res->duration_ns / (double)NSEC_PER_SEC) / |
| 251 | + rate_divisor; |
| 252 | + hits_per_prod = hits_per_sec / env.producer_cnt; |
| 253 | + |
| 254 | + printf("Iter %3d (%7.3lfus): ", iter, |
| 255 | + (delta_ns - NSEC_PER_SEC) / 1000.0); |
| 256 | + printf("hits %8.3lf%c/s (%7.3lf%c/prod)\n", hits_per_sec, rate, |
| 257 | + hits_per_prod, rate); |
| 258 | +} |
| 259 | + |
| 260 | +static void free_ops_report_final(struct bench_res res[], int res_cnt) |
| 261 | +{ |
| 262 | + double hits_mean = 0.0, hits_stddev = 0.0; |
| 263 | + double lat_divisor = 1000000.0; |
| 264 | + double rate_divisor = 1000.0; |
| 265 | + const char *unit = "ms"; |
| 266 | + double latency = 0.0; |
| 267 | + char rate = 'K'; |
| 268 | + int i; |
| 269 | + |
| 270 | + for (i = 0; i < res_cnt; i++) { |
| 271 | + double val = res[i].hits / rate_divisor / |
| 272 | + (res[i].duration_ns / (double)NSEC_PER_SEC); |
| 273 | + hits_mean += val / (0.0 + res_cnt); |
| 274 | + latency += res[i].duration_ns / res[i].hits / (0.0 + res_cnt); |
| 275 | + } |
| 276 | + |
| 277 | + if (res_cnt > 1) { |
| 278 | + for (i = 0; i < res_cnt; i++) { |
| 279 | + double val = |
| 280 | + res[i].hits / rate_divisor / |
| 281 | + (res[i].duration_ns / (double)NSEC_PER_SEC); |
| 282 | + hits_stddev += (hits_mean - val) * (hits_mean - val) / |
| 283 | + (res_cnt - 1.0); |
| 284 | + } |
| 285 | + |
| 286 | + hits_stddev = sqrt(hits_stddev); |
| 287 | + } |
| 288 | + printf("Summary: throughput %8.3lf \u00B1 %5.3lf %c ops/s (%7.3lf%c ops/prod), ", |
| 289 | + hits_mean, hits_stddev, rate, hits_mean / env.producer_cnt, |
| 290 | + rate); |
| 291 | + printf("latency %8.3lf %s/op\n", |
| 292 | + latency / lat_divisor / env.producer_cnt, unit); |
| 293 | +} |
| 294 | + |
| 295 | +const struct bench bench_lpm_trie_lookup = { |
| 296 | + .name = "lpm-trie-lookup", |
| 297 | + .argp = &bench_lpm_trie_map_argp, |
| 298 | + .validate = __lpm_validate, |
| 299 | + .setup = lpm_lookup_setup, |
| 300 | + .producer_thread = lpm_producer, |
| 301 | + .measure = lpm_measure, |
| 302 | + .report_progress = ops_report_progress, |
| 303 | + .report_final = ops_report_final, |
| 304 | +}; |
| 305 | + |
| 306 | +const struct bench bench_lpm_trie_update = { |
| 307 | + .name = "lpm-trie-update", |
| 308 | + .argp = &bench_lpm_trie_map_argp, |
| 309 | + .validate = __lpm_validate, |
| 310 | + .setup = lpm_update_setup, |
| 311 | + .producer_thread = lpm_producer, |
| 312 | + .measure = lpm_measure, |
| 313 | + .report_progress = ops_report_progress, |
| 314 | + .report_final = ops_report_final, |
| 315 | +}; |
| 316 | + |
| 317 | +const struct bench bench_lpm_trie_delete = { |
| 318 | + .name = "lpm-trie-delete", |
| 319 | + .argp = &bench_lpm_trie_map_argp, |
| 320 | + .validate = lpm_delete_validate, |
| 321 | + .setup = lpm_delete_setup, |
| 322 | + .producer_thread = lpm_producer, |
| 323 | + .measure = lpm_measure, |
| 324 | + .report_progress = ops_report_progress, |
| 325 | + .report_final = ops_report_final, |
| 326 | +}; |
| 327 | + |
| 328 | +const struct bench bench_lpm_trie_free = { |
| 329 | + .name = "lpm-trie-free", |
| 330 | + .argp = &bench_lpm_trie_map_argp, |
| 331 | + .validate = lpm_free_validate, |
| 332 | + .setup = lpm_free_setup, |
| 333 | + .producer_thread = lpm_free_producer, |
| 334 | + .measure = lpm_measure, |
| 335 | + .report_progress = free_ops_report_progress, |
| 336 | + .report_final = free_ops_report_final, |
| 337 | +}; |
0 commit comments