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 hashmap_lookup_setup(enum bpf_map_type map_type) 152 { 153 struct bpf_link *link; 154 __u32 map_flags; 155 int map_fd; 156 int ret; 157 int i; 158 159 setup_libbpf(); 160 161 ctx.skel = bpf_hashmap_lookup__open(); 162 if (!ctx.skel) { 163 fprintf(stderr, "failed to open skeleton\n"); 164 exit(1); 165 } 166 167 map_flags = args.map_flags; 168 if (map_type == BPF_MAP_TYPE_RHASH) 169 map_flags |= BPF_F_NO_PREALLOC; 170 171 bpf_map__set_type(ctx.skel->maps.hash_map_bench, map_type); 172 bpf_map__set_max_entries(ctx.skel->maps.hash_map_bench, args.max_entries); 173 bpf_map__set_key_size(ctx.skel->maps.hash_map_bench, args.key_size); 174 bpf_map__set_value_size(ctx.skel->maps.hash_map_bench, 8); 175 bpf_map__set_map_flags(ctx.skel->maps.hash_map_bench, map_flags); 176 177 ctx.skel->bss->nr_entries = args.nr_entries; 178 ctx.skel->bss->nr_loops = args.nr_loops / args.nr_entries; 179 180 if (args.key_size > 4) { 181 for (i = 1; i < args.key_size/4; i++) 182 ctx.skel->bss->key[i] = 2654435761 * i; 183 } 184 185 ret = bpf_hashmap_lookup__load(ctx.skel); 186 if (ret) { 187 bpf_hashmap_lookup__destroy(ctx.skel); 188 fprintf(stderr, "failed to load map: %s", strerror(-ret)); 189 exit(1); 190 } 191 192 /* fill in the hash_map */ 193 map_fd = bpf_map__fd(ctx.skel->maps.hash_map_bench); 194 for (u64 i = 0; i < args.nr_entries; i++) { 195 patch_key(i, ctx.skel->bss->key); 196 bpf_map_update_elem(map_fd, ctx.skel->bss->key, &i, BPF_ANY); 197 } 198 199 link = bpf_program__attach(ctx.skel->progs.benchmark); 200 if (!link) { 201 fprintf(stderr, "failed to attach program!\n"); 202 exit(1); 203 } 204 } 205 206 static void setup(void) 207 { 208 hashmap_lookup_setup(BPF_MAP_TYPE_HASH); 209 } 210 211 static void rhash_setup(void) 212 { 213 hashmap_lookup_setup(BPF_MAP_TYPE_RHASH); 214 } 215 216 static inline double events_from_time(u64 time) 217 { 218 if (time) 219 return args.nr_loops * 1000000000llu / time / 1000000.0L; 220 221 return 0; 222 } 223 224 static int compute_events(u64 *times, double *events_mean, double *events_stddev, u64 *mean_time) 225 { 226 int i, n = 0; 227 228 *events_mean = 0; 229 *events_stddev = 0; 230 *mean_time = 0; 231 232 for (i = 0; i < 32; i++) { 233 if (!times[i]) 234 break; 235 *mean_time += times[i]; 236 *events_mean += events_from_time(times[i]); 237 n += 1; 238 } 239 if (!n) 240 return 0; 241 242 *mean_time /= n; 243 *events_mean /= n; 244 245 if (n > 1) { 246 for (i = 0; i < n; i++) { 247 double events_i = *events_mean - events_from_time(times[i]); 248 *events_stddev += events_i * events_i / (n - 1); 249 } 250 *events_stddev = sqrt(*events_stddev); 251 } 252 253 return n; 254 } 255 256 static void hashmap_report_final(struct bench_res res[], int res_cnt) 257 { 258 unsigned int nr_cpus = bpf_num_possible_cpus(); 259 double events_mean, events_stddev; 260 u64 mean_time; 261 int i, n; 262 263 for (i = 0; i < nr_cpus; i++) { 264 n = compute_events(ctx.skel->bss->percpu_times[i], &events_mean, 265 &events_stddev, &mean_time); 266 if (n == 0) 267 continue; 268 269 if (env.quiet) { 270 /* we expect only one cpu to be present */ 271 if (env.affinity) 272 printf("%.3lf\n", events_mean); 273 else 274 printf("cpu%02d %.3lf\n", i, events_mean); 275 } else { 276 printf("cpu%02d: lookup %.3lfM ± %.3lfM events/sec" 277 " (approximated from %d samples of ~%lums)\n", 278 i, events_mean, 2*events_stddev, 279 n, mean_time / 1000000); 280 } 281 } 282 } 283 284 const struct bench bench_bpf_hashmap_lookup = { 285 .name = "bpf-hashmap-lookup", 286 .argp = &bench_hashmap_lookup_argp, 287 .validate = validate, 288 .setup = setup, 289 .producer_thread = producer, 290 .measure = measure, 291 .report_progress = NULL, 292 .report_final = hashmap_report_final, 293 }; 294 295 const struct bench bench_bpf_rhashmap_lookup = { 296 .name = "bpf-rhashmap-lookup", 297 .argp = &bench_hashmap_lookup_argp, 298 .validate = validate, 299 .setup = rhash_setup, 300 .producer_thread = producer, 301 .measure = measure, 302 .report_progress = NULL, 303 .report_final = hashmap_report_final, 304 }; 305