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