| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* Copyright (c) 2023 Isovalent */ |
| 3 | |
| 4 | #include <sys/random.h> |
| 5 | #include <argp.h> |
| 6 | #include "bench.h" |
| 7 | #include "bpf_hashmap_lookup.skel.h" |
| 8 | #include "bpf_util.h" |
| 9 | |
| 10 | /* BPF triggering benchmarks */ |
| 11 | static struct ctx { |
| 12 | struct bpf_hashmap_lookup *skel; |
| 13 | } ctx; |
| 14 | |
| 15 | /* only available to kernel, so define it here */ |
| 16 | #define BPF_MAX_LOOPS (1<<23) |
| 17 | |
| 18 | #define MAX_KEY_SIZE 1024 /* the size of the key map */ |
| 19 | |
| 20 | static struct { |
| 21 | __u32 key_size; |
| 22 | __u32 map_flags; |
| 23 | __u32 max_entries; |
| 24 | __u32 nr_entries; |
| 25 | __u32 nr_loops; |
| 26 | } args = { |
| 27 | .key_size = 4, |
| 28 | .map_flags = 0, |
| 29 | .max_entries = 1000, |
| 30 | .nr_entries = 500, |
| 31 | .nr_loops = 1000000, |
| 32 | }; |
| 33 | |
| 34 | enum { |
| 35 | ARG_KEY_SIZE = 8001, |
| 36 | ARG_MAP_FLAGS, |
| 37 | ARG_MAX_ENTRIES, |
| 38 | ARG_NR_ENTRIES, |
| 39 | ARG_NR_LOOPS, |
| 40 | }; |
| 41 | |
| 42 | static const struct argp_option opts[] = { |
| 43 | { "key_size" , ARG_KEY_SIZE, "KEY_SIZE" , 0, |
| 44 | "The hashmap key size (max 1024)" }, |
| 45 | { "map_flags" , ARG_MAP_FLAGS, "MAP_FLAGS" , 0, |
| 46 | "The hashmap flags passed to BPF_MAP_CREATE" }, |
| 47 | { "max_entries" , ARG_MAX_ENTRIES, "MAX_ENTRIES" , 0, |
| 48 | "The hashmap max entries" }, |
| 49 | { "nr_entries" , ARG_NR_ENTRIES, "NR_ENTRIES" , 0, |
| 50 | "The number of entries to insert/lookup" }, |
| 51 | { "nr_loops" , ARG_NR_LOOPS, "NR_LOOPS" , 0, |
| 52 | "The number of loops for the benchmark" }, |
| 53 | {}, |
| 54 | }; |
| 55 | |
| 56 | static error_t parse_arg(int key, char *arg, struct argp_state *state) |
| 57 | { |
| 58 | long ret; |
| 59 | |
| 60 | switch (key) { |
| 61 | case ARG_KEY_SIZE: |
| 62 | ret = strtol(arg, NULL, 10); |
| 63 | if (ret < 1 || ret > MAX_KEY_SIZE) { |
| 64 | fprintf(stderr, "invalid key_size" ); |
| 65 | argp_usage(state); |
| 66 | } |
| 67 | args.key_size = ret; |
| 68 | break; |
| 69 | case ARG_MAP_FLAGS: |
| 70 | ret = strtol(arg, NULL, 0); |
| 71 | if (ret < 0 || ret > UINT_MAX) { |
| 72 | fprintf(stderr, "invalid map_flags" ); |
| 73 | argp_usage(state); |
| 74 | } |
| 75 | args.map_flags = ret; |
| 76 | break; |
| 77 | case ARG_MAX_ENTRIES: |
| 78 | ret = strtol(arg, NULL, 10); |
| 79 | if (ret < 1 || ret > UINT_MAX) { |
| 80 | fprintf(stderr, "invalid max_entries" ); |
| 81 | argp_usage(state); |
| 82 | } |
| 83 | args.max_entries = ret; |
| 84 | break; |
| 85 | case ARG_NR_ENTRIES: |
| 86 | ret = strtol(arg, NULL, 10); |
| 87 | if (ret < 1 || ret > UINT_MAX) { |
| 88 | fprintf(stderr, "invalid nr_entries" ); |
| 89 | argp_usage(state); |
| 90 | } |
| 91 | args.nr_entries = ret; |
| 92 | break; |
| 93 | case ARG_NR_LOOPS: |
| 94 | ret = strtol(arg, NULL, 10); |
| 95 | if (ret < 1 || ret > BPF_MAX_LOOPS) { |
| 96 | fprintf(stderr, "invalid nr_loops: %ld (min=1 max=%u)\n" , |
| 97 | ret, BPF_MAX_LOOPS); |
| 98 | argp_usage(state); |
| 99 | } |
| 100 | args.nr_loops = ret; |
| 101 | break; |
| 102 | default: |
| 103 | return ARGP_ERR_UNKNOWN; |
| 104 | } |
| 105 | |
| 106 | return 0; |
| 107 | } |
| 108 | |
| 109 | const struct argp bench_hashmap_lookup_argp = { |
| 110 | .options = opts, |
| 111 | .parser = parse_arg, |
| 112 | }; |
| 113 | |
| 114 | static void validate(void) |
| 115 | { |
| 116 | if (env.consumer_cnt != 0) { |
| 117 | fprintf(stderr, "benchmark doesn't support consumer!\n" ); |
| 118 | exit(1); |
| 119 | } |
| 120 | |
| 121 | if (args.nr_entries > args.max_entries) { |
| 122 | fprintf(stderr, "args.nr_entries is too big! (max %u, got %u)\n" , |
| 123 | args.max_entries, args.nr_entries); |
| 124 | exit(1); |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | static void *producer(void *input) |
| 129 | { |
| 130 | while (true) { |
| 131 | /* trigger the bpf program */ |
| 132 | syscall(__NR_getpgid); |
| 133 | } |
| 134 | return NULL; |
| 135 | } |
| 136 | |
| 137 | static void measure(struct bench_res *res) |
| 138 | { |
| 139 | } |
| 140 | |
| 141 | static inline void patch_key(u32 i, u32 *key) |
| 142 | { |
| 143 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ |
| 144 | *key = i + 1; |
| 145 | #else |
| 146 | *key = __builtin_bswap32(i + 1); |
| 147 | #endif |
| 148 | /* the rest of key is random */ |
| 149 | } |
| 150 | |
| 151 | static void setup(void) |
| 152 | { |
| 153 | struct bpf_link *link; |
| 154 | int map_fd; |
| 155 | int ret; |
| 156 | int i; |
| 157 | |
| 158 | setup_libbpf(); |
| 159 | |
| 160 | ctx.skel = bpf_hashmap_lookup__open(); |
| 161 | if (!ctx.skel) { |
| 162 | fprintf(stderr, "failed to open skeleton\n" ); |
| 163 | exit(1); |
| 164 | } |
| 165 | |
| 166 | bpf_map__set_max_entries(ctx.skel->maps.hash_map_bench, args.max_entries); |
| 167 | bpf_map__set_key_size(ctx.skel->maps.hash_map_bench, args.key_size); |
| 168 | bpf_map__set_value_size(ctx.skel->maps.hash_map_bench, 8); |
| 169 | bpf_map__set_map_flags(ctx.skel->maps.hash_map_bench, args.map_flags); |
| 170 | |
| 171 | ctx.skel->bss->nr_entries = args.nr_entries; |
| 172 | ctx.skel->bss->nr_loops = args.nr_loops / args.nr_entries; |
| 173 | |
| 174 | if (args.key_size > 4) { |
| 175 | for (i = 1; i < args.key_size/4; i++) |
| 176 | ctx.skel->bss->key[i] = 2654435761 * i; |
| 177 | } |
| 178 | |
| 179 | ret = bpf_hashmap_lookup__load(ctx.skel); |
| 180 | if (ret) { |
| 181 | bpf_hashmap_lookup__destroy(ctx.skel); |
| 182 | fprintf(stderr, "failed to load map: %s" , strerror(-ret)); |
| 183 | exit(1); |
| 184 | } |
| 185 | |
| 186 | /* fill in the hash_map */ |
| 187 | map_fd = bpf_map__fd(ctx.skel->maps.hash_map_bench); |
| 188 | for (u64 i = 0; i < args.nr_entries; i++) { |
| 189 | patch_key(i, ctx.skel->bss->key); |
| 190 | bpf_map_update_elem(map_fd, ctx.skel->bss->key, &i, BPF_ANY); |
| 191 | } |
| 192 | |
| 193 | link = bpf_program__attach(ctx.skel->progs.benchmark); |
| 194 | if (!link) { |
| 195 | fprintf(stderr, "failed to attach program!\n" ); |
| 196 | exit(1); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | static inline double events_from_time(u64 time) |
| 201 | { |
| 202 | if (time) |
| 203 | return args.nr_loops * 1000000000llu / time / 1000000.0L; |
| 204 | |
| 205 | return 0; |
| 206 | } |
| 207 | |
| 208 | static int compute_events(u64 *times, double *events_mean, double *events_stddev, u64 *mean_time) |
| 209 | { |
| 210 | int i, n = 0; |
| 211 | |
| 212 | *events_mean = 0; |
| 213 | *events_stddev = 0; |
| 214 | *mean_time = 0; |
| 215 | |
| 216 | for (i = 0; i < 32; i++) { |
| 217 | if (!times[i]) |
| 218 | break; |
| 219 | *mean_time += times[i]; |
| 220 | *events_mean += events_from_time(times[i]); |
| 221 | n += 1; |
| 222 | } |
| 223 | if (!n) |
| 224 | return 0; |
| 225 | |
| 226 | *mean_time /= n; |
| 227 | *events_mean /= n; |
| 228 | |
| 229 | if (n > 1) { |
| 230 | for (i = 0; i < n; i++) { |
| 231 | double events_i = *events_mean - events_from_time(times[i]); |
| 232 | *events_stddev += events_i * events_i / (n - 1); |
| 233 | } |
| 234 | *events_stddev = sqrt(*events_stddev); |
| 235 | } |
| 236 | |
| 237 | return n; |
| 238 | } |
| 239 | |
| 240 | static void hashmap_report_final(struct bench_res res[], int res_cnt) |
| 241 | { |
| 242 | unsigned int nr_cpus = bpf_num_possible_cpus(); |
| 243 | double events_mean, events_stddev; |
| 244 | u64 mean_time; |
| 245 | int i, n; |
| 246 | |
| 247 | for (i = 0; i < nr_cpus; i++) { |
| 248 | n = compute_events(ctx.skel->bss->percpu_times[i], &events_mean, |
| 249 | &events_stddev, &mean_time); |
| 250 | if (n == 0) |
| 251 | continue; |
| 252 | |
| 253 | if (env.quiet) { |
| 254 | /* we expect only one cpu to be present */ |
| 255 | if (env.affinity) |
| 256 | printf("%.3lf\n" , events_mean); |
| 257 | else |
| 258 | printf("cpu%02d %.3lf\n" , i, events_mean); |
| 259 | } else { |
| 260 | printf("cpu%02d: lookup %.3lfM ± %.3lfM events/sec" |
| 261 | " (approximated from %d samples of ~%lums)\n" , |
| 262 | i, events_mean, 2*events_stddev, |
| 263 | n, mean_time / 1000000); |
| 264 | } |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | const struct bench bench_bpf_hashmap_lookup = { |
| 269 | .name = "bpf-hashmap-lookup" , |
| 270 | .argp = &bench_hashmap_lookup_argp, |
| 271 | .validate = validate, |
| 272 | .setup = setup, |
| 273 | .producer_thread = producer, |
| 274 | .measure = measure, |
| 275 | .report_progress = NULL, |
| 276 | .report_final = hashmap_report_final, |
| 277 | }; |
| 278 | |