// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2023 Isovalent */ #include <sys/random.h> #include <argp.h> #include "bench.h" #include "bpf_hashmap_lookup.skel.h" #include "bpf_util.h" /* BPF triggering benchmarks */ static struct ctx { struct bpf_hashmap_lookup *skel; } ctx; /* only available to kernel, so define it here */ #define BPF_MAX_LOOPS (1<<23) #define MAX_KEY_SIZE 1024 /* the size of the key map */ static struct { __u32 key_size; __u32 map_flags; __u32 max_entries; __u32 nr_entries; __u32 nr_loops; } args = { .key_size = 4, .map_flags = 0, .max_entries = 1000, .nr_entries = 500, .nr_loops = 1000000, }; enum { ARG_KEY_SIZE = 8001, ARG_MAP_FLAGS, ARG_MAX_ENTRIES, ARG_NR_ENTRIES, ARG_NR_LOOPS, }; static const struct argp_option opts[] = { { "key_size", ARG_KEY_SIZE, "KEY_SIZE", 0, "The hashmap key size (max 1024)"}, { "map_flags", ARG_MAP_FLAGS, "MAP_FLAGS", 0, "The hashmap flags passed to BPF_MAP_CREATE"}, { "max_entries", ARG_MAX_ENTRIES, "MAX_ENTRIES", 0, "The hashmap max entries"}, { "nr_entries", ARG_NR_ENTRIES, "NR_ENTRIES", 0, "The number of entries to insert/lookup"}, { "nr_loops", ARG_NR_LOOPS, "NR_LOOPS", 0, "The number of loops for the benchmark"}, {}, }; static error_t parse_arg(int key, char *arg, struct argp_state *state) { long ret; switch (key) { case ARG_KEY_SIZE: ret = strtol(arg, NULL, 10); if (ret < 1 || ret > MAX_KEY_SIZE) { fprintf(stderr, "invalid key_size"); argp_usage(state); } args.key_size = ret; break; case ARG_MAP_FLAGS: ret = strtol(arg, NULL, 0); if (ret < 0 || ret > UINT_MAX) { fprintf(stderr, "invalid map_flags"); argp_usage(state); } args.map_flags = ret; break; case ARG_MAX_ENTRIES: ret = strtol(arg, NULL, 10); if (ret < 1 || ret > UINT_MAX) { fprintf(stderr, "invalid max_entries"); argp_usage(state); } args.max_entries = ret; break; case ARG_NR_ENTRIES: ret = strtol(arg, NULL, 10); if (ret < 1 || ret > UINT_MAX) { fprintf(stderr, "invalid nr_entries"); argp_usage(state); } args.nr_entries = ret; break; case ARG_NR_LOOPS: ret = strtol(arg, NULL, 10); if (ret < 1 || ret > BPF_MAX_LOOPS) { fprintf(stderr, "invalid nr_loops: %ld (min=1 max=%u)\n", ret, BPF_MAX_LOOPS); argp_usage(state); } args.nr_loops = ret; break; default: return ARGP_ERR_UNKNOWN; } return 0; } const struct argp bench_hashmap_lookup_argp = { .options = opts, .parser = parse_arg, }; static void validate(void) { if (env.consumer_cnt != 0) { fprintf(stderr, "benchmark doesn't support consumer!\n"); exit(1); } if (args.nr_entries > args.max_entries) { fprintf(stderr, "args.nr_entries is too big! (max %u, got %u)\n", args.max_entries, args.nr_entries); exit(1); } } static void *producer(void *input) { while (true) { /* trigger the bpf program */ syscall(__NR_getpgid); } return NULL; } static void measure(struct bench_res *res) { } static inline void patch_key(u32 i, u32 *key) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ *key = i + 1; #else *key = __builtin_bswap32(i + 1); #endif /* the rest of key is random */ } static void setup(void) { struct bpf_link *link; int map_fd; int ret; int i; setup_libbpf(); ctx.skel = bpf_hashmap_lookup__open(); if (!ctx.skel) { fprintf(stderr, "failed to open skeleton\n"); exit(1); } bpf_map__set_max_entries(ctx.skel->maps.hash_map_bench, args.max_entries); bpf_map__set_key_size(ctx.skel->maps.hash_map_bench, args.key_size); bpf_map__set_value_size(ctx.skel->maps.hash_map_bench, 8); bpf_map__set_map_flags(ctx.skel->maps.hash_map_bench, args.map_flags); ctx.skel->bss->nr_entries = args.nr_entries; ctx.skel->bss->nr_loops = args.nr_loops / args.nr_entries; if (args.key_size > 4) { for (i = 1; i < args.key_size/4; i++) ctx.skel->bss->key[i] = 2654435761 * i; } ret = bpf_hashmap_lookup__load(ctx.skel); if (ret) { bpf_hashmap_lookup__destroy(ctx.skel); fprintf(stderr, "failed to load map: %s", strerror(-ret)); exit(1); } /* fill in the hash_map */ map_fd = bpf_map__fd(ctx.skel->maps.hash_map_bench); for (u64 i = 0; i < args.nr_entries; i++) { patch_key(i, ctx.skel->bss->key); bpf_map_update_elem(map_fd, ctx.skel->bss->key, &i, BPF_ANY); } link = bpf_program__attach(ctx.skel->progs.benchmark); if (!link) { fprintf(stderr, "failed to attach program!\n"); exit(1); } } static inline double events_from_time(u64 time) { if (time) return args.nr_loops * 1000000000llu / time / 1000000.0L; return 0; } static int compute_events(u64 *times, double *events_mean, double *events_stddev, u64 *mean_time) { int i, n = 0; *events_mean = 0; *events_stddev = 0; *mean_time = 0; for (i = 0; i < 32; i++) { if (!times[i]) break; *mean_time += times[i]; *events_mean += events_from_time(times[i]); n += 1; } if (!n) return 0; *mean_time /= n; *events_mean /= n; if (n > 1) { for (i = 0; i < n; i++) { double events_i = *events_mean - events_from_time(times[i]); *events_stddev += events_i * events_i / (n - 1); } *events_stddev = sqrt(*events_stddev); } return n; } static void hashmap_report_final(struct bench_res res[], int res_cnt) { unsigned int nr_cpus = bpf_num_possible_cpus(); double events_mean, events_stddev; u64 mean_time; int i, n; for (i = 0; i < nr_cpus; i++) { n = compute_events(ctx.skel->bss->percpu_times[i], &events_mean, &events_stddev, &mean_time); if (n == 0) continue; if (env.quiet) { /* we expect only one cpu to be present */ if (env.affinity) printf("%.3lf\n", events_mean); else printf("cpu%02d %.3lf\n", i, events_mean); } else { printf("cpu%02d: lookup %.3lfM ± %.3lfM events/sec" " (approximated from %d samples of ~%lums)\n", i, events_mean, 2*events_stddev, n, mean_time / 1000000); } } } const struct bench bench_bpf_hashmap_lookup = { .name = "bpf-hashmap-lookup", .argp = &bench_hashmap_lookup_argp, .validate = validate, .setup = setup, .producer_thread = producer, .measure = measure, .report_progress = NULL, .report_final = hashmap_report_final, };