// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ #include <sys/types.h> #include <sys/socket.h> #include <pthread.h> #include <argp.h> #include "bench.h" #include "bench_local_storage_create.skel.h" struct thread { int *fds; pthread_t *pthds; int *pthd_results; }; static struct bench_local_storage_create *skel; static struct thread *threads; static long create_owner_errs; static int storage_type = BPF_MAP_TYPE_SK_STORAGE; static int batch_sz = 32; enum { ARG_BATCH_SZ = 9000, ARG_STORAGE_TYPE = 9001, }; static const struct argp_option opts[] = { { "batch-size", ARG_BATCH_SZ, "BATCH_SIZE", 0, "The number of storage creations in each batch" }, { "storage-type", ARG_STORAGE_TYPE, "STORAGE_TYPE", 0, "The type of local storage to test (socket or task)" }, {}, }; static error_t parse_arg(int key, char *arg, struct argp_state *state) { int ret; switch (key) { case ARG_BATCH_SZ: ret = atoi(arg); if (ret < 1) { fprintf(stderr, "invalid batch-size\n"); argp_usage(state); } batch_sz = ret; break; case ARG_STORAGE_TYPE: if (!strcmp(arg, "task")) { storage_type = BPF_MAP_TYPE_TASK_STORAGE; } else if (!strcmp(arg, "socket")) { storage_type = BPF_MAP_TYPE_SK_STORAGE; } else { fprintf(stderr, "invalid storage-type (socket or task)\n"); argp_usage(state); } break; default: return ARGP_ERR_UNKNOWN; } return 0; } const struct argp bench_local_storage_create_argp = { .options = opts, .parser = parse_arg, }; static void validate(void) { if (env.consumer_cnt != 0) { fprintf(stderr, "local-storage-create benchmark does not need consumer\n"); exit(1); } } static void setup(void) { int i; skel = bench_local_storage_create__open_and_load(); if (!skel) { fprintf(stderr, "error loading skel\n"); exit(1); } skel->bss->bench_pid = getpid(); if (storage_type == BPF_MAP_TYPE_SK_STORAGE) { if (!bpf_program__attach(skel->progs.socket_post_create)) { fprintf(stderr, "Error attaching bpf program\n"); exit(1); } } else { if (!bpf_program__attach(skel->progs.sched_process_fork)) { fprintf(stderr, "Error attaching bpf program\n"); exit(1); } } if (!bpf_program__attach(skel->progs.kmalloc)) { fprintf(stderr, "Error attaching bpf program\n"); exit(1); } threads = calloc(env.producer_cnt, sizeof(*threads)); if (!threads) { fprintf(stderr, "cannot alloc thread_res\n"); exit(1); } for (i = 0; i < env.producer_cnt; i++) { struct thread *t = &threads[i]; if (storage_type == BPF_MAP_TYPE_SK_STORAGE) { t->fds = malloc(batch_sz * sizeof(*t->fds)); if (!t->fds) { fprintf(stderr, "cannot alloc t->fds\n"); exit(1); } } else { t->pthds = malloc(batch_sz * sizeof(*t->pthds)); if (!t->pthds) { fprintf(stderr, "cannot alloc t->pthds\n"); exit(1); } t->pthd_results = malloc(batch_sz * sizeof(*t->pthd_results)); if (!t->pthd_results) { fprintf(stderr, "cannot alloc t->pthd_results\n"); exit(1); } } } } static void measure(struct bench_res *res) { res->hits = atomic_swap(&skel->bss->create_cnts, 0); res->drops = atomic_swap(&skel->bss->kmalloc_cnts, 0); } static void *sk_producer(void *input) { struct thread *t = &threads[(long)(input)]; int *fds = t->fds; int i; while (true) { for (i = 0; i < batch_sz; i++) { fds[i] = socket(AF_INET6, SOCK_DGRAM, 0); if (fds[i] == -1) atomic_inc(&create_owner_errs); } for (i = 0; i < batch_sz; i++) { if (fds[i] != -1) close(fds[i]); } } return NULL; } static void *thread_func(void *arg) { return NULL; } static void *task_producer(void *input) { struct thread *t = &threads[(long)(input)]; pthread_t *pthds = t->pthds; int *pthd_results = t->pthd_results; int i; while (true) { for (i = 0; i < batch_sz; i++) { pthd_results[i] = pthread_create(&pthds[i], NULL, thread_func, NULL); if (pthd_results[i]) atomic_inc(&create_owner_errs); } for (i = 0; i < batch_sz; i++) { if (!pthd_results[i]) pthread_join(pthds[i], NULL);; } } return NULL; } static void *producer(void *input) { if (storage_type == BPF_MAP_TYPE_SK_STORAGE) return sk_producer(input); else return task_producer(input); } static void report_progress(int iter, struct bench_res *res, long delta_ns) { double creates_per_sec, kmallocs_per_create; creates_per_sec = res->hits / 1000.0 / (delta_ns / 1000000000.0); kmallocs_per_create = (double)res->drops / res->hits; printf("Iter %3d (%7.3lfus): ", iter, (delta_ns - 1000000000) / 1000.0); printf("creates %8.3lfk/s (%7.3lfk/prod), ", creates_per_sec, creates_per_sec / env.producer_cnt); printf("%3.2lf kmallocs/create\n", kmallocs_per_create); } static void report_final(struct bench_res res[], int res_cnt) { double creates_mean = 0.0, creates_stddev = 0.0; long total_creates = 0, total_kmallocs = 0; int i; for (i = 0; i < res_cnt; i++) { creates_mean += res[i].hits / 1000.0 / (0.0 + res_cnt); total_creates += res[i].hits; total_kmallocs += res[i].drops; } if (res_cnt > 1) { for (i = 0; i < res_cnt; i++) creates_stddev += (creates_mean - res[i].hits / 1000.0) * (creates_mean - res[i].hits / 1000.0) / (res_cnt - 1.0); creates_stddev = sqrt(creates_stddev); } printf("Summary: creates %8.3lf \u00B1 %5.3lfk/s (%7.3lfk/prod), ", creates_mean, creates_stddev, creates_mean / env.producer_cnt); printf("%4.2lf kmallocs/create\n", (double)total_kmallocs / total_creates); if (create_owner_errs || skel->bss->create_errs) printf("%s() errors %ld create_errs %ld\n", storage_type == BPF_MAP_TYPE_SK_STORAGE ? "socket" : "pthread_create", create_owner_errs, skel->bss->create_errs); } /* Benchmark performance of creating bpf local storage */ const struct bench bench_local_storage_create = { .name = "local-storage-create", .argp = &bench_local_storage_create_argp, .validate = validate, .setup = setup, .producer_thread = producer, .measure = measure, .report_progress = report_progress, .report_final = report_final, };