1// SPDX-License-Identifier: GPL-2.0
2/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
3
4#include <sys/types.h>
5#include <sys/socket.h>
6#include <pthread.h>
7#include <argp.h>
8
9#include "bench.h"
10#include "bench_local_storage_create.skel.h"
11
12struct thread {
13	int *fds;
14	pthread_t *pthds;
15	int *pthd_results;
16};
17
18static struct bench_local_storage_create *skel;
19static struct thread *threads;
20static long create_owner_errs;
21static int storage_type = BPF_MAP_TYPE_SK_STORAGE;
22static int batch_sz = 32;
23
24enum {
25	ARG_BATCH_SZ = 9000,
26	ARG_STORAGE_TYPE = 9001,
27};
28
29static const struct argp_option opts[] = {
30	{ "batch-size", ARG_BATCH_SZ, "BATCH_SIZE", 0,
31	  "The number of storage creations in each batch" },
32	{ "storage-type", ARG_STORAGE_TYPE, "STORAGE_TYPE", 0,
33	  "The type of local storage to test (socket or task)" },
34	{},
35};
36
37static error_t parse_arg(int key, char *arg, struct argp_state *state)
38{
39	int ret;
40
41	switch (key) {
42	case ARG_BATCH_SZ:
43		ret = atoi(arg);
44		if (ret < 1) {
45			fprintf(stderr, "invalid batch-size\n");
46			argp_usage(state);
47		}
48		batch_sz = ret;
49		break;
50	case ARG_STORAGE_TYPE:
51		if (!strcmp(arg, "task")) {
52			storage_type = BPF_MAP_TYPE_TASK_STORAGE;
53		} else if (!strcmp(arg, "socket")) {
54			storage_type = BPF_MAP_TYPE_SK_STORAGE;
55		} else {
56			fprintf(stderr, "invalid storage-type (socket or task)\n");
57			argp_usage(state);
58		}
59		break;
60	default:
61		return ARGP_ERR_UNKNOWN;
62	}
63
64	return 0;
65}
66
67const struct argp bench_local_storage_create_argp = {
68	.options = opts,
69	.parser = parse_arg,
70};
71
72static void validate(void)
73{
74	if (env.consumer_cnt != 0) {
75		fprintf(stderr,
76			"local-storage-create benchmark does not need consumer\n");
77		exit(1);
78	}
79}
80
81static void setup(void)
82{
83	int i;
84
85	skel = bench_local_storage_create__open_and_load();
86	if (!skel) {
87		fprintf(stderr, "error loading skel\n");
88		exit(1);
89	}
90
91	skel->bss->bench_pid = getpid();
92	if (storage_type == BPF_MAP_TYPE_SK_STORAGE) {
93		if (!bpf_program__attach(skel->progs.socket_post_create)) {
94			fprintf(stderr, "Error attaching bpf program\n");
95			exit(1);
96		}
97	} else {
98		if (!bpf_program__attach(skel->progs.sched_process_fork)) {
99			fprintf(stderr, "Error attaching bpf program\n");
100			exit(1);
101		}
102	}
103
104	if (!bpf_program__attach(skel->progs.kmalloc)) {
105		fprintf(stderr, "Error attaching bpf program\n");
106		exit(1);
107	}
108
109	threads = calloc(env.producer_cnt, sizeof(*threads));
110
111	if (!threads) {
112		fprintf(stderr, "cannot alloc thread_res\n");
113		exit(1);
114	}
115
116	for (i = 0; i < env.producer_cnt; i++) {
117		struct thread *t = &threads[i];
118
119		if (storage_type == BPF_MAP_TYPE_SK_STORAGE) {
120			t->fds = malloc(batch_sz * sizeof(*t->fds));
121			if (!t->fds) {
122				fprintf(stderr, "cannot alloc t->fds\n");
123				exit(1);
124			}
125		} else {
126			t->pthds = malloc(batch_sz * sizeof(*t->pthds));
127			if (!t->pthds) {
128				fprintf(stderr, "cannot alloc t->pthds\n");
129				exit(1);
130			}
131			t->pthd_results = malloc(batch_sz * sizeof(*t->pthd_results));
132			if (!t->pthd_results) {
133				fprintf(stderr, "cannot alloc t->pthd_results\n");
134				exit(1);
135			}
136		}
137	}
138}
139
140static void measure(struct bench_res *res)
141{
142	res->hits = atomic_swap(&skel->bss->create_cnts, 0);
143	res->drops = atomic_swap(&skel->bss->kmalloc_cnts, 0);
144}
145
146static void *sk_producer(void *input)
147{
148	struct thread *t = &threads[(long)(input)];
149	int *fds = t->fds;
150	int i;
151
152	while (true) {
153		for (i = 0; i < batch_sz; i++) {
154			fds[i] = socket(AF_INET6, SOCK_DGRAM, 0);
155			if (fds[i] == -1)
156				atomic_inc(&create_owner_errs);
157		}
158
159		for (i = 0; i < batch_sz; i++) {
160			if (fds[i] != -1)
161				close(fds[i]);
162		}
163	}
164
165	return NULL;
166}
167
168static void *thread_func(void *arg)
169{
170	return NULL;
171}
172
173static void *task_producer(void *input)
174{
175	struct thread *t = &threads[(long)(input)];
176	pthread_t *pthds = t->pthds;
177	int *pthd_results = t->pthd_results;
178	int i;
179
180	while (true) {
181		for (i = 0; i < batch_sz; i++) {
182			pthd_results[i] = pthread_create(&pthds[i], NULL, thread_func, NULL);
183			if (pthd_results[i])
184				atomic_inc(&create_owner_errs);
185		}
186
187		for (i = 0; i < batch_sz; i++) {
188			if (!pthd_results[i])
189				pthread_join(pthds[i], NULL);;
190		}
191	}
192
193	return NULL;
194}
195
196static void *producer(void *input)
197{
198	if (storage_type == BPF_MAP_TYPE_SK_STORAGE)
199		return sk_producer(input);
200	else
201		return task_producer(input);
202}
203
204static void report_progress(int iter, struct bench_res *res, long delta_ns)
205{
206	double creates_per_sec, kmallocs_per_create;
207
208	creates_per_sec = res->hits / 1000.0 / (delta_ns / 1000000000.0);
209	kmallocs_per_create = (double)res->drops / res->hits;
210
211	printf("Iter %3d (%7.3lfus): ",
212	       iter, (delta_ns - 1000000000) / 1000.0);
213	printf("creates %8.3lfk/s (%7.3lfk/prod), ",
214	       creates_per_sec, creates_per_sec / env.producer_cnt);
215	printf("%3.2lf kmallocs/create\n", kmallocs_per_create);
216}
217
218static void report_final(struct bench_res res[], int res_cnt)
219{
220	double creates_mean = 0.0, creates_stddev = 0.0;
221	long total_creates = 0, total_kmallocs = 0;
222	int i;
223
224	for (i = 0; i < res_cnt; i++) {
225		creates_mean += res[i].hits / 1000.0 / (0.0 + res_cnt);
226		total_creates += res[i].hits;
227		total_kmallocs += res[i].drops;
228	}
229
230	if (res_cnt > 1)  {
231		for (i = 0; i < res_cnt; i++)
232			creates_stddev += (creates_mean - res[i].hits / 1000.0) *
233				       (creates_mean - res[i].hits / 1000.0) /
234				       (res_cnt - 1.0);
235		creates_stddev = sqrt(creates_stddev);
236	}
237	printf("Summary: creates %8.3lf \u00B1 %5.3lfk/s (%7.3lfk/prod), ",
238	       creates_mean, creates_stddev, creates_mean / env.producer_cnt);
239	printf("%4.2lf kmallocs/create\n", (double)total_kmallocs / total_creates);
240	if (create_owner_errs || skel->bss->create_errs)
241		printf("%s() errors %ld create_errs %ld\n",
242		       storage_type == BPF_MAP_TYPE_SK_STORAGE ?
243		       "socket" : "pthread_create",
244		       create_owner_errs,
245		       skel->bss->create_errs);
246}
247
248/* Benchmark performance of creating bpf local storage  */
249const struct bench bench_local_storage_create = {
250	.name = "local-storage-create",
251	.argp = &bench_local_storage_create_argp,
252	.validate = validate,
253	.setup = setup,
254	.producer_thread = producer,
255	.measure = measure,
256	.report_progress = report_progress,
257	.report_final = report_final,
258};
259