root/tools/testing/selftests/bpf/benchs/bench_bpf_hashmap_lookup.c
// 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,
};