xref: /linux/tools/perf/bench/sched-pipe.c (revision 63af040b0b4067d75e59df9fa093f3f5dbc5899e)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  *
4  * sched-pipe.c
5  *
6  * pipe: Benchmark for pipe()
7  *
8  * Based on pipe-test-1m.c by Ingo Molnar <mingo@redhat.com>
9  *  http://people.redhat.com/mingo/cfs-scheduler/tools/pipe-test-1m.c
10  * Ported to perf by Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp>
11  */
12 #include <subcmd/parse-options.h>
13 #include <api/fs/fs.h>
14 #include "bench.h"
15 #include "util/cgroup.h"
16 
17 #include <unistd.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <signal.h>
21 #include <sys/wait.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <limits.h>
26 #include <assert.h>
27 #include <poll.h>
28 #include <sys/epoll.h>
29 #include <sys/time.h>
30 #include <sys/types.h>
31 #include <sys/syscall.h>
32 #include <linux/time64.h>
33 
34 #include <pthread.h>
35 
36 struct thread_data {
37 	int			nr;
38 	int			pipe_read;
39 	int			pipe_write;
40 	struct epoll_event      epoll_ev;
41 	int			epoll_fd;
42 	bool			cgroup_failed;
43 	pthread_t		pthread;
44 	char			*buf;
45 };
46 
47 #define LOOPS_DEFAULT 1000000
48 static	int			loops = LOOPS_DEFAULT;
49 
50 /* Use processes by default: */
51 static bool			threaded;
52 
53 static bool			nonblocking;
54 static unsigned int		write_size = sizeof(int);
55 static char			*cgrp_names[2];
56 static struct cgroup		*cgrps[2];
57 
58 static int parse_two_cgroups(const struct option *opt __maybe_unused,
59 			     const char *str, int unset __maybe_unused)
60 {
61 	char *p = strdup(str);
62 	char *q;
63 	int ret = -1;
64 
65 	if (p == NULL) {
66 		fprintf(stderr, "memory allocation failure\n");
67 		return -1;
68 	}
69 
70 	q = strchr(p, ',');
71 	if (q == NULL) {
72 		fprintf(stderr, "it should have two cgroup names: %s\n", p);
73 		goto out;
74 	}
75 	*q = '\0';
76 
77 	cgrp_names[0] = strdup(p);
78 	cgrp_names[1] = strdup(q + 1);
79 
80 	if (cgrp_names[0] == NULL || cgrp_names[1] == NULL) {
81 		fprintf(stderr, "memory allocation failure\n");
82 		goto out;
83 	}
84 	ret = 0;
85 
86 out:
87 	free(p);
88 	return ret;
89 }
90 
91 static const struct option options[] = {
92 	OPT_BOOLEAN('n', "nonblocking",	&nonblocking,	"Use non-blocking operations"),
93 	OPT_INTEGER('l', "loop",	&loops,		"Specify number of loops"),
94 	OPT_BOOLEAN('T', "threaded",	&threaded,	"Specify threads/process based task setup"),
95 	OPT_UINTEGER('s', "write-size", &write_size,
96 		     "Bytes per ping-pong write (default 4-bytes). Use larger values to exercise the pipe page-allocation path."),
97 	OPT_CALLBACK('G', "cgroups", NULL, "SEND,RECV",
98 		     "Put sender and receivers in given cgroups",
99 		     parse_two_cgroups),
100 	OPT_END()
101 };
102 
103 static const char * const bench_sched_pipe_usage[] = {
104 	"perf bench sched pipe <options>",
105 	NULL
106 };
107 
108 static int enter_cgroup(int nr)
109 {
110 	char buf[32];
111 	int fd, len, ret;
112 	int saved_errno;
113 	struct cgroup *cgrp;
114 	pid_t pid;
115 
116 	if (cgrp_names[nr] == NULL)
117 		return 0;
118 
119 	if (cgrps[nr] == NULL) {
120 		cgrps[nr] = cgroup__new(cgrp_names[nr], /*do_open=*/true);
121 		if (cgrps[nr] == NULL)
122 			goto err;
123 	}
124 	cgrp = cgrps[nr];
125 
126 	if (threaded)
127 		pid = syscall(__NR_gettid);
128 	else
129 		pid = getpid();
130 
131 	snprintf(buf, sizeof(buf), "%d\n", pid);
132 	len = strlen(buf);
133 
134 	/* try cgroup v2 interface first */
135 	if (threaded)
136 		fd = openat(cgrp->fd, "cgroup.threads", O_WRONLY);
137 	else
138 		fd = openat(cgrp->fd, "cgroup.procs", O_WRONLY);
139 
140 	/* try cgroup v1 if failed */
141 	if (fd < 0 && errno == ENOENT)
142 		fd = openat(cgrp->fd, "tasks", O_WRONLY);
143 
144 	if (fd < 0)
145 		goto err;
146 
147 	ret = write(fd, buf, len);
148 	close(fd);
149 
150 	if (ret != len) {
151 		printf("Cannot enter to cgroup: %s\n", cgrp->name);
152 		return -1;
153 	}
154 	return 0;
155 
156 err:
157 	saved_errno = errno;
158 	printf("Failed to open cgroup file in %s\n", cgrp_names[nr]);
159 
160 	if (saved_errno == ENOENT) {
161 		char mnt[PATH_MAX];
162 
163 		if (cgroupfs_find_mountpoint(mnt, sizeof(mnt), "perf_event") == 0)
164 			printf(" Hint: create the cgroup first, like 'mkdir %s/%s'\n",
165 			       mnt, cgrp_names[nr]);
166 	} else if (saved_errno == EACCES && geteuid() > 0) {
167 		printf(" Hint: try to run as root\n");
168 	}
169 
170 	return -1;
171 }
172 
173 static void exit_cgroup(int nr)
174 {
175 	cgroup__put(cgrps[nr]);
176 	free(cgrp_names[nr]);
177 }
178 
179 /* Sleep until @fd is writable, so we don't busy-spin on EWOULDBLOCK. */
180 static inline void wait_writable(int fd)
181 {
182 	struct pollfd pfd = {
183 		.fd	= fd,
184 		.events	= POLLOUT,
185 	};
186 
187 	poll(&pfd, 1, -1);
188 }
189 
190 /*
191  * Loop on short read()/write(): the kernel may return fewer bytes than
192  * requested, retry on EINTR, and in non-blocking mode wait via poll()
193  * when the writer transiently hits EWOULDBLOCK while the peer is still
194  * draining a full pipe (capacity is sized to write_size).
195  */
196 static inline int write_pipe(struct thread_data *td)
197 {
198 	unsigned int done = 0;
199 	int ret;
200 
201 	while (done < write_size) {
202 		ret = write(td->pipe_write, td->buf + done, write_size - done);
203 		if (ret < 0) {
204 			if (errno == EINTR)
205 				continue;
206 			if (nonblocking && errno == EWOULDBLOCK) {
207 				wait_writable(td->pipe_write);
208 				continue;
209 			}
210 			return ret;
211 		}
212 		done += ret;
213 	}
214 	return done;
215 }
216 
217 static inline int read_pipe(struct thread_data *td)
218 {
219 	unsigned int done = 0;
220 	int ret;
221 
222 	while (done < write_size) {
223 		if (nonblocking) {
224 			ret = epoll_wait(td->epoll_fd, &td->epoll_ev, 1, -1);
225 			if (ret < 0) {
226 				if (errno == EINTR)
227 					continue;
228 				return ret;
229 			}
230 		}
231 		ret = read(td->pipe_read, td->buf + done, write_size - done);
232 		if (ret < 0) {
233 			if (errno == EINTR)
234 				continue;
235 			if (nonblocking && errno == EWOULDBLOCK)
236 				continue;
237 			return ret;
238 		}
239 		if (ret == 0)
240 			return done;
241 		done += ret;
242 	}
243 	return done;
244 }
245 
246 static void *worker_thread(void *__tdata)
247 {
248 	struct thread_data *td = __tdata;
249 	int i, ret;
250 
251 	ret = enter_cgroup(td->nr);
252 	if (ret < 0) {
253 		td->cgroup_failed = true;
254 		return NULL;
255 	}
256 
257 	if (nonblocking) {
258 		td->epoll_ev.events = EPOLLIN;
259 		td->epoll_fd = epoll_create(1);
260 		BUG_ON(td->epoll_fd < 0);
261 		BUG_ON(epoll_ctl(td->epoll_fd, EPOLL_CTL_ADD, td->pipe_read, &td->epoll_ev) < 0);
262 	}
263 
264 	for (i = 0; i < loops; i++) {
265 		ret = write_pipe(td);
266 		BUG_ON(ret != (int)write_size);
267 		ret = read_pipe(td);
268 		BUG_ON(ret != (int)write_size);
269 	}
270 
271 	return NULL;
272 }
273 
274 /*
275  * On a custom write_size, resize the pipes so a single payload fits.
276  */
277 static int resize_pipes(int wfd1, int wfd2)
278 {
279 	int r1, r2;
280 
281 	if (write_size <= sizeof(int))
282 		return 0;
283 
284 	r1 = fcntl(wfd1, F_SETPIPE_SZ, write_size);
285 	r2 = fcntl(wfd2, F_SETPIPE_SZ, write_size);
286 	if (r1 < 0 || r2 < 0 ||
287 	    (unsigned int)r1 < write_size ||
288 	    (unsigned int)r2 < write_size) {
289 		fprintf(stderr,
290 			"--write-size %u exceeds /proc/sys/fs/pipe-max-size\n",
291 			write_size);
292 		return -1;
293 	}
294 	return 0;
295 }
296 
297 int bench_sched_pipe(int argc, const char **argv)
298 {
299 	struct thread_data threads[2] = {};
300 	struct thread_data *td;
301 	int pipe_1[2], pipe_2[2];
302 	struct timeval start, stop, diff;
303 	unsigned long long result_usec = 0;
304 	int nr_threads = 2;
305 	int t;
306 
307 	/*
308 	 * why does "ret" exist?
309 	 * discarding returned value of read(), write()
310 	 * causes error in building environment for perf
311 	 */
312 	int __maybe_unused ret, wait_stat, flags = 0;
313 	pid_t pid, retpid __maybe_unused;
314 
315 	argc = parse_options(argc, argv, options, bench_sched_pipe_usage, 0);
316 
317 	/*
318 	 * The error paths below return early without closing the pipes or
319 	 * freeing the cgroup state. That is fine: bench_sched_pipe() runs
320 	 * once and the process exits right after it returns, so these are
321 	 * not real leaks.
322 	 */
323 	if (write_size == 0 || write_size > INT_MAX) {
324 		fprintf(stderr, "--write-size must be in 1..%d\n", INT_MAX);
325 		return -1;
326 	}
327 
328 	if (nonblocking)
329 		flags |= O_NONBLOCK;
330 
331 	BUG_ON(pipe2(pipe_1, flags));
332 	BUG_ON(pipe2(pipe_2, flags));
333 
334 	if (resize_pipes(pipe_1[1], pipe_2[1]) < 0)
335 		return -1;
336 
337 	for (t = 0; t < nr_threads; t++) {
338 		threads[t].buf = calloc(1, write_size);
339 		BUG_ON(!threads[t].buf);
340 	}
341 
342 	gettimeofday(&start, NULL);
343 
344 	for (t = 0; t < nr_threads; t++) {
345 		td = threads + t;
346 
347 		td->nr = t;
348 
349 		if (t == 0) {
350 			td->pipe_read = pipe_1[0];
351 			td->pipe_write = pipe_2[1];
352 		} else {
353 			td->pipe_write = pipe_1[1];
354 			td->pipe_read = pipe_2[0];
355 		}
356 	}
357 
358 	if (threaded) {
359 		for (t = 0; t < nr_threads; t++) {
360 			td = threads + t;
361 
362 			ret = pthread_create(&td->pthread, NULL, worker_thread, td);
363 			BUG_ON(ret);
364 		}
365 
366 		for (t = 0; t < nr_threads; t++) {
367 			td = threads + t;
368 
369 			ret = pthread_join(td->pthread, NULL);
370 			BUG_ON(ret);
371 		}
372 	} else {
373 		pid = fork();
374 		assert(pid >= 0);
375 
376 		if (!pid) {
377 			worker_thread(threads + 0);
378 			exit(0);
379 		} else {
380 			worker_thread(threads + 1);
381 		}
382 
383 		retpid = waitpid(pid, &wait_stat, 0);
384 		assert((retpid == pid) && WIFEXITED(wait_stat));
385 	}
386 
387 	gettimeofday(&stop, NULL);
388 	timersub(&stop, &start, &diff);
389 
390 	for (t = 0; t < nr_threads; t++)
391 		free(threads[t].buf);
392 
393 	exit_cgroup(0);
394 	exit_cgroup(1);
395 
396 	if (threads[0].cgroup_failed || threads[1].cgroup_failed)
397 		return 0;
398 
399 	switch (bench_format) {
400 	case BENCH_FORMAT_DEFAULT:
401 		printf("# Executed %d pipe operations between two %s\n\n",
402 			loops, threaded ? "threads" : "processes");
403 
404 		result_usec = diff.tv_sec * USEC_PER_SEC;
405 		result_usec += diff.tv_usec;
406 
407 		printf(" %14s: %lu.%03lu [sec]\n\n", "Total time",
408 		       (unsigned long) diff.tv_sec,
409 		       (unsigned long) (diff.tv_usec / USEC_PER_MSEC));
410 
411 		printf(" %14lf usecs/op\n",
412 		       (double)result_usec / (double)loops);
413 		printf(" %14d ops/sec\n",
414 		       (int)((double)loops /
415 			     ((double)result_usec / (double)USEC_PER_SEC)));
416 		break;
417 
418 	case BENCH_FORMAT_SIMPLE:
419 		printf("%lu.%03lu\n",
420 		       (unsigned long) diff.tv_sec,
421 		       (unsigned long) (diff.tv_usec / USEC_PER_MSEC));
422 		break;
423 
424 	default:
425 		/* reaching here is something disaster */
426 		fprintf(stderr, "Unknown format:%d\n", bench_format);
427 		exit(1);
428 		break;
429 	}
430 
431 	return 0;
432 }
433