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