| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */ |
| |
| #include <argp.h> |
| #include <linux/btf.h> |
| |
| #include "local_storage_bench.skel.h" |
| #include "bench.h" |
| |
| #include <test_btf.h> |
| |
| static struct { |
| __u32 nr_maps; |
| __u32 hashmap_nr_keys_used; |
| } args = { |
| .nr_maps = 1000, |
| .hashmap_nr_keys_used = 1000, |
| }; |
| |
| enum { |
| ARG_NR_MAPS = 6000, |
| ARG_HASHMAP_NR_KEYS_USED = 6001, |
| }; |
| |
| static const struct argp_option opts[] = { |
| { "nr_maps", ARG_NR_MAPS, "NR_MAPS", 0, |
| "Set number of local_storage maps"}, |
| { "hashmap_nr_keys_used", ARG_HASHMAP_NR_KEYS_USED, "NR_KEYS", |
| 0, "When doing hashmap test, set number of hashmap keys test uses"}, |
| {}, |
| }; |
| |
| static error_t parse_arg(int key, char *arg, struct argp_state *state) |
| { |
| long ret; |
| |
| switch (key) { |
| case ARG_NR_MAPS: |
| ret = strtol(arg, NULL, 10); |
| if (ret < 1 || ret > UINT_MAX) { |
| fprintf(stderr, "invalid nr_maps"); |
| argp_usage(state); |
| } |
| args.nr_maps = ret; |
| break; |
| case ARG_HASHMAP_NR_KEYS_USED: |
| ret = strtol(arg, NULL, 10); |
| if (ret < 1 || ret > UINT_MAX) { |
| fprintf(stderr, "invalid hashmap_nr_keys_used"); |
| argp_usage(state); |
| } |
| args.hashmap_nr_keys_used = ret; |
| break; |
| default: |
| return ARGP_ERR_UNKNOWN; |
| } |
| |
| return 0; |
| } |
| |
| const struct argp bench_local_storage_argp = { |
| .options = opts, |
| .parser = parse_arg, |
| }; |
| |
| /* Keep in sync w/ array of maps in bpf */ |
| #define MAX_NR_MAPS 1000 |
| /* keep in sync w/ same define in bpf */ |
| #define HASHMAP_SZ 4194304 |
| |
| static void validate(void) |
| { |
| if (env.producer_cnt != 1) { |
| fprintf(stderr, "benchmark doesn't support multi-producer!\n"); |
| exit(1); |
| } |
| if (env.consumer_cnt != 0) { |
| fprintf(stderr, "benchmark doesn't support consumer!\n"); |
| exit(1); |
| } |
| |
| if (args.nr_maps > MAX_NR_MAPS) { |
| fprintf(stderr, "nr_maps must be <= 1000\n"); |
| exit(1); |
| } |
| |
| if (args.hashmap_nr_keys_used > HASHMAP_SZ) { |
| fprintf(stderr, "hashmap_nr_keys_used must be <= %u\n", HASHMAP_SZ); |
| exit(1); |
| } |
| } |
| |
| static struct { |
| struct local_storage_bench *skel; |
| void *bpf_obj; |
| struct bpf_map *array_of_maps; |
| } ctx; |
| |
| static void prepopulate_hashmap(int fd) |
| { |
| int i, key, val; |
| |
| /* local_storage gets will have BPF_LOCAL_STORAGE_GET_F_CREATE flag set, so |
| * populate the hashmap for a similar comparison |
| */ |
| for (i = 0; i < HASHMAP_SZ; i++) { |
| key = val = i; |
| if (bpf_map_update_elem(fd, &key, &val, 0)) { |
| fprintf(stderr, "Error prepopulating hashmap (key %d)\n", key); |
| exit(1); |
| } |
| } |
| } |
| |
| static void __setup(struct bpf_program *prog, bool hashmap) |
| { |
| struct bpf_map *inner_map; |
| int i, fd, mim_fd, err; |
| |
| LIBBPF_OPTS(bpf_map_create_opts, create_opts); |
| |
| if (!hashmap) |
| create_opts.map_flags = BPF_F_NO_PREALLOC; |
| |
| ctx.skel->rodata->num_maps = args.nr_maps; |
| ctx.skel->rodata->hashmap_num_keys = args.hashmap_nr_keys_used; |
| inner_map = bpf_map__inner_map(ctx.array_of_maps); |
| create_opts.btf_key_type_id = bpf_map__btf_key_type_id(inner_map); |
| create_opts.btf_value_type_id = bpf_map__btf_value_type_id(inner_map); |
| |
| err = local_storage_bench__load(ctx.skel); |
| if (err) { |
| fprintf(stderr, "Error loading skeleton\n"); |
| goto err_out; |
| } |
| |
| create_opts.btf_fd = bpf_object__btf_fd(ctx.skel->obj); |
| |
| mim_fd = bpf_map__fd(ctx.array_of_maps); |
| if (mim_fd < 0) { |
| fprintf(stderr, "Error getting map_in_map fd\n"); |
| goto err_out; |
| } |
| |
| for (i = 0; i < args.nr_maps; i++) { |
| if (hashmap) |
| fd = bpf_map_create(BPF_MAP_TYPE_HASH, NULL, sizeof(int), |
| sizeof(int), HASHMAP_SZ, &create_opts); |
| else |
| fd = bpf_map_create(BPF_MAP_TYPE_TASK_STORAGE, NULL, sizeof(int), |
| sizeof(int), 0, &create_opts); |
| if (fd < 0) { |
| fprintf(stderr, "Error creating map %d: %d\n", i, fd); |
| goto err_out; |
| } |
| |
| if (hashmap) |
| prepopulate_hashmap(fd); |
| |
| err = bpf_map_update_elem(mim_fd, &i, &fd, 0); |
| if (err) { |
| fprintf(stderr, "Error updating array-of-maps w/ map %d\n", i); |
| goto err_out; |
| } |
| } |
| |
| if (!bpf_program__attach(prog)) { |
| fprintf(stderr, "Error attaching bpf program\n"); |
| goto err_out; |
| } |
| |
| return; |
| err_out: |
| exit(1); |
| } |
| |
| static void hashmap_setup(void) |
| { |
| struct local_storage_bench *skel; |
| |
| setup_libbpf(); |
| |
| skel = local_storage_bench__open(); |
| ctx.skel = skel; |
| ctx.array_of_maps = skel->maps.array_of_hash_maps; |
| skel->rodata->use_hashmap = 1; |
| skel->rodata->interleave = 0; |
| |
| __setup(skel->progs.get_local, true); |
| } |
| |
| static void local_storage_cache_get_setup(void) |
| { |
| struct local_storage_bench *skel; |
| |
| setup_libbpf(); |
| |
| skel = local_storage_bench__open(); |
| ctx.skel = skel; |
| ctx.array_of_maps = skel->maps.array_of_local_storage_maps; |
| skel->rodata->use_hashmap = 0; |
| skel->rodata->interleave = 0; |
| |
| __setup(skel->progs.get_local, false); |
| } |
| |
| static void local_storage_cache_get_interleaved_setup(void) |
| { |
| struct local_storage_bench *skel; |
| |
| setup_libbpf(); |
| |
| skel = local_storage_bench__open(); |
| ctx.skel = skel; |
| ctx.array_of_maps = skel->maps.array_of_local_storage_maps; |
| skel->rodata->use_hashmap = 0; |
| skel->rodata->interleave = 1; |
| |
| __setup(skel->progs.get_local, false); |
| } |
| |
| static void measure(struct bench_res *res) |
| { |
| res->hits = atomic_swap(&ctx.skel->bss->hits, 0); |
| res->important_hits = atomic_swap(&ctx.skel->bss->important_hits, 0); |
| } |
| |
| static inline void trigger_bpf_program(void) |
| { |
| syscall(__NR_getpgid); |
| } |
| |
| static void *producer(void *input) |
| { |
| while (true) |
| trigger_bpf_program(); |
| |
| return NULL; |
| } |
| |
| /* cache sequential and interleaved get benchs test local_storage get |
| * performance, specifically they demonstrate performance cliff of |
| * current list-plus-cache local_storage model. |
| * |
| * cache sequential get: call bpf_task_storage_get on n maps in order |
| * cache interleaved get: like "sequential get", but interleave 4 calls to the |
| * 'important' map (idx 0 in array_of_maps) for every 10 calls. Goal |
| * is to mimic environment where many progs are accessing their local_storage |
| * maps, with 'our' prog needing to access its map more often than others |
| */ |
| const struct bench bench_local_storage_cache_seq_get = { |
| .name = "local-storage-cache-seq-get", |
| .argp = &bench_local_storage_argp, |
| .validate = validate, |
| .setup = local_storage_cache_get_setup, |
| .producer_thread = producer, |
| .measure = measure, |
| .report_progress = local_storage_report_progress, |
| .report_final = local_storage_report_final, |
| }; |
| |
| const struct bench bench_local_storage_cache_interleaved_get = { |
| .name = "local-storage-cache-int-get", |
| .argp = &bench_local_storage_argp, |
| .validate = validate, |
| .setup = local_storage_cache_get_interleaved_setup, |
| .producer_thread = producer, |
| .measure = measure, |
| .report_progress = local_storage_report_progress, |
| .report_final = local_storage_report_final, |
| }; |
| |
| const struct bench bench_local_storage_cache_hashmap_control = { |
| .name = "local-storage-cache-hashmap-control", |
| .argp = &bench_local_storage_argp, |
| .validate = validate, |
| .setup = hashmap_setup, |
| .producer_thread = producer, |
| .measure = measure, |
| .report_progress = local_storage_report_progress, |
| .report_final = local_storage_report_final, |
| }; |