xref: /linux/tools/perf/bench/sched-pipe.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
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 <assert.h>
26 #include <sys/time.h>
27 #include <sys/types.h>
28 #include <sys/syscall.h>
29 #include <linux/time64.h>
30 
31 #include <pthread.h>
32 
33 struct thread_data {
34 	int			nr;
35 	int			pipe_read;
36 	int			pipe_write;
37 	bool			cgroup_failed;
38 	pthread_t		pthread;
39 };
40 
41 #define LOOPS_DEFAULT 1000000
42 static	int			loops = LOOPS_DEFAULT;
43 
44 /* Use processes by default: */
45 static bool			threaded;
46 
47 static char			*cgrp_names[2];
48 static struct cgroup		*cgrps[2];
49 
50 static int parse_two_cgroups(const struct option *opt __maybe_unused,
51 			     const char *str, int unset __maybe_unused)
52 {
53 	char *p = strdup(str);
54 	char *q;
55 	int ret = -1;
56 
57 	if (p == NULL) {
58 		fprintf(stderr, "memory allocation failure\n");
59 		return -1;
60 	}
61 
62 	q = strchr(p, ',');
63 	if (q == NULL) {
64 		fprintf(stderr, "it should have two cgroup names: %s\n", p);
65 		goto out;
66 	}
67 	*q = '\0';
68 
69 	cgrp_names[0] = strdup(p);
70 	cgrp_names[1] = strdup(q + 1);
71 
72 	if (cgrp_names[0] == NULL || cgrp_names[1] == NULL) {
73 		fprintf(stderr, "memory allocation failure\n");
74 		goto out;
75 	}
76 	ret = 0;
77 
78 out:
79 	free(p);
80 	return ret;
81 }
82 
83 static const struct option options[] = {
84 	OPT_INTEGER('l', "loop",	&loops,		"Specify number of loops"),
85 	OPT_BOOLEAN('T', "threaded",	&threaded,	"Specify threads/process based task setup"),
86 	OPT_CALLBACK('G', "cgroups", NULL, "SEND,RECV",
87 		     "Put sender and receivers in given cgroups",
88 		     parse_two_cgroups),
89 	OPT_END()
90 };
91 
92 static const char * const bench_sched_pipe_usage[] = {
93 	"perf bench sched pipe <options>",
94 	NULL
95 };
96 
97 static int enter_cgroup(int nr)
98 {
99 	char buf[32];
100 	int fd, len, ret;
101 	int saved_errno;
102 	struct cgroup *cgrp;
103 	pid_t pid;
104 
105 	if (cgrp_names[nr] == NULL)
106 		return 0;
107 
108 	if (cgrps[nr] == NULL) {
109 		cgrps[nr] = cgroup__new(cgrp_names[nr], /*do_open=*/true);
110 		if (cgrps[nr] == NULL)
111 			goto err;
112 	}
113 	cgrp = cgrps[nr];
114 
115 	if (threaded)
116 		pid = syscall(__NR_gettid);
117 	else
118 		pid = getpid();
119 
120 	snprintf(buf, sizeof(buf), "%d\n", pid);
121 	len = strlen(buf);
122 
123 	/* try cgroup v2 interface first */
124 	if (threaded)
125 		fd = openat(cgrp->fd, "cgroup.threads", O_WRONLY);
126 	else
127 		fd = openat(cgrp->fd, "cgroup.procs", O_WRONLY);
128 
129 	/* try cgroup v1 if failed */
130 	if (fd < 0 && errno == ENOENT)
131 		fd = openat(cgrp->fd, "tasks", O_WRONLY);
132 
133 	if (fd < 0)
134 		goto err;
135 
136 	ret = write(fd, buf, len);
137 	close(fd);
138 
139 	if (ret != len) {
140 		printf("Cannot enter to cgroup: %s\n", cgrp->name);
141 		return -1;
142 	}
143 	return 0;
144 
145 err:
146 	saved_errno = errno;
147 	printf("Failed to open cgroup file in %s\n", cgrp_names[nr]);
148 
149 	if (saved_errno == ENOENT) {
150 		char mnt[PATH_MAX];
151 
152 		if (cgroupfs_find_mountpoint(mnt, sizeof(mnt), "perf_event") == 0)
153 			printf(" Hint: create the cgroup first, like 'mkdir %s/%s'\n",
154 			       mnt, cgrp_names[nr]);
155 	} else if (saved_errno == EACCES && geteuid() > 0) {
156 		printf(" Hint: try to run as root\n");
157 	}
158 
159 	return -1;
160 }
161 
162 static void exit_cgroup(int nr)
163 {
164 	cgroup__put(cgrps[nr]);
165 	free(cgrp_names[nr]);
166 }
167 
168 static void *worker_thread(void *__tdata)
169 {
170 	struct thread_data *td = __tdata;
171 	int m = 0, i;
172 	int ret;
173 
174 	ret = enter_cgroup(td->nr);
175 	if (ret < 0) {
176 		td->cgroup_failed = true;
177 		return NULL;
178 	}
179 
180 	for (i = 0; i < loops; i++) {
181 		if (!td->nr) {
182 			ret = read(td->pipe_read, &m, sizeof(int));
183 			BUG_ON(ret != sizeof(int));
184 			ret = write(td->pipe_write, &m, sizeof(int));
185 			BUG_ON(ret != sizeof(int));
186 		} else {
187 			ret = write(td->pipe_write, &m, sizeof(int));
188 			BUG_ON(ret != sizeof(int));
189 			ret = read(td->pipe_read, &m, sizeof(int));
190 			BUG_ON(ret != sizeof(int));
191 		}
192 	}
193 
194 	return NULL;
195 }
196 
197 int bench_sched_pipe(int argc, const char **argv)
198 {
199 	struct thread_data threads[2] = {};
200 	struct thread_data *td;
201 	int pipe_1[2], pipe_2[2];
202 	struct timeval start, stop, diff;
203 	unsigned long long result_usec = 0;
204 	int nr_threads = 2;
205 	int t;
206 
207 	/*
208 	 * why does "ret" exist?
209 	 * discarding returned value of read(), write()
210 	 * causes error in building environment for perf
211 	 */
212 	int __maybe_unused ret, wait_stat;
213 	pid_t pid, retpid __maybe_unused;
214 
215 	argc = parse_options(argc, argv, options, bench_sched_pipe_usage, 0);
216 
217 	BUG_ON(pipe(pipe_1));
218 	BUG_ON(pipe(pipe_2));
219 
220 	gettimeofday(&start, NULL);
221 
222 	for (t = 0; t < nr_threads; t++) {
223 		td = threads + t;
224 
225 		td->nr = t;
226 
227 		if (t == 0) {
228 			td->pipe_read = pipe_1[0];
229 			td->pipe_write = pipe_2[1];
230 		} else {
231 			td->pipe_write = pipe_1[1];
232 			td->pipe_read = pipe_2[0];
233 		}
234 	}
235 
236 	if (threaded) {
237 		for (t = 0; t < nr_threads; t++) {
238 			td = threads + t;
239 
240 			ret = pthread_create(&td->pthread, NULL, worker_thread, td);
241 			BUG_ON(ret);
242 		}
243 
244 		for (t = 0; t < nr_threads; t++) {
245 			td = threads + t;
246 
247 			ret = pthread_join(td->pthread, NULL);
248 			BUG_ON(ret);
249 		}
250 	} else {
251 		pid = fork();
252 		assert(pid >= 0);
253 
254 		if (!pid) {
255 			worker_thread(threads + 0);
256 			exit(0);
257 		} else {
258 			worker_thread(threads + 1);
259 		}
260 
261 		retpid = waitpid(pid, &wait_stat, 0);
262 		assert((retpid == pid) && WIFEXITED(wait_stat));
263 	}
264 
265 	gettimeofday(&stop, NULL);
266 	timersub(&stop, &start, &diff);
267 
268 	exit_cgroup(0);
269 	exit_cgroup(1);
270 
271 	if (threads[0].cgroup_failed || threads[1].cgroup_failed)
272 		return 0;
273 
274 	switch (bench_format) {
275 	case BENCH_FORMAT_DEFAULT:
276 		printf("# Executed %d pipe operations between two %s\n\n",
277 			loops, threaded ? "threads" : "processes");
278 
279 		result_usec = diff.tv_sec * USEC_PER_SEC;
280 		result_usec += diff.tv_usec;
281 
282 		printf(" %14s: %lu.%03lu [sec]\n\n", "Total time",
283 		       (unsigned long) diff.tv_sec,
284 		       (unsigned long) (diff.tv_usec / USEC_PER_MSEC));
285 
286 		printf(" %14lf usecs/op\n",
287 		       (double)result_usec / (double)loops);
288 		printf(" %14d ops/sec\n",
289 		       (int)((double)loops /
290 			     ((double)result_usec / (double)USEC_PER_SEC)));
291 		break;
292 
293 	case BENCH_FORMAT_SIMPLE:
294 		printf("%lu.%03lu\n",
295 		       (unsigned long) diff.tv_sec,
296 		       (unsigned long) (diff.tv_usec / USEC_PER_MSEC));
297 		break;
298 
299 	default:
300 		/* reaching here is something disaster */
301 		fprintf(stderr, "Unknown format:%d\n", bench_format);
302 		exit(1);
303 		break;
304 	}
305 
306 	return 0;
307 }
308