| // 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, |
| }; |