[v2] perf bench sched pipe: Add -G/--cgroups option
Commit Message
The -G/--cgroups option is to put sender and receiver in different
cgroups in order to measure cgroup context switch overheads.
Users need to make sure the cgroups exist and accessible.
# perf stat -e context-switches,cgroup-switches \
> taskset -c 0 perf bench sched pipe -l 10000 > /dev/null
Performance counter stats for 'taskset -c 0 perf bench sched pipe -l 10000':
20,001 context-switches
2 cgroup-switches
0.053449651 seconds time elapsed
0.011286000 seconds user
0.041869000 seconds sys
# perf stat -e context-switches,cgroup-switches \
> taskset -c 0 perf bench sched pipe -l 10000 -G AAA,BBB > /dev/null
Performance counter stats for 'taskset -c 0 perf bench sched pipe -l 10000 -G AAA,BBB':
20,001 context-switches
20,001 cgroup-switches
0.052768627 seconds time elapsed
0.006284000 seconds user
0.046266000 seconds sys
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
---
tools/perf/Documentation/perf-bench.txt | 19 +++++
tools/perf/bench/sched-pipe.c | 93 +++++++++++++++++++++++++
2 files changed, 112 insertions(+)
Comments
* Namhyung Kim <namhyung@kernel.org> wrote:
> + cgrp_send = cgroup__new(p, /*do_open=*/true);
> + if (cgrp_send == NULL) {
> + fprintf(stderr, "cannot open sender cgroup: %s", p);
> + goto out;
> + }
Maybe in this case print out a small suggestion of how to create this
particular cgroup?
Most distro users and even kernel developers don't ever have to create
new cgroups.
Maybe even allow the creation of new cgroups for this testing, if they
don't already exist? As long as we don't delete any cgroups I don't think
much harm can be done - and the increase in usability is substantial.
> +static void enter_cgroup(struct cgroup *cgrp)
> +{
> + char buf[32];
> + int fd, len;
> + pid_t pid;
> +
> + if (cgrp == NULL)
> + return;
> +
> + if (threaded)
> + pid = syscall(__NR_gettid);
> + else
> + pid = getpid();
> +
> + snprintf(buf, sizeof(buf), "%d\n", pid);
> + len = strlen(buf);
> +
> + /* try cgroup v2 interface first */
> + if (threaded)
> + fd = openat(cgrp->fd, "cgroup.threads", O_WRONLY);
> + else
> + fd = openat(cgrp->fd, "cgroup.procs", O_WRONLY);
> +
> + /* try cgroup v1 if failed */
> + if (fd < 0)
> + fd = openat(cgrp->fd, "tasks", O_WRONLY);
> +
> + if (fd < 0) {
> + printf("failed to open cgroup file in %s\n", cgrp->name);
> + return;
> + }
> +
> + if (write(fd, buf, len) != len)
> + printf("cannot enter to cgroup: %s\n", cgrp->name);
The failures here should probably result in termination of the run with an
error code, not just messages which are easy to skip in automated tests?
Thanks,
Ingo
On Sat, Oct 14, 2023 at 1:44 AM Ingo Molnar <mingo@kernel.org> wrote:
>
>
> * Namhyung Kim <namhyung@kernel.org> wrote:
>
> > + cgrp_send = cgroup__new(p, /*do_open=*/true);
> > + if (cgrp_send == NULL) {
> > + fprintf(stderr, "cannot open sender cgroup: %s", p);
> > + goto out;
> > + }
>
> Maybe in this case print out a small suggestion of how to create this
> particular cgroup?
>
> Most distro users and even kernel developers don't ever have to create
> new cgroups.
>
> Maybe even allow the creation of new cgroups for this testing, if they
> don't already exist? As long as we don't delete any cgroups I don't think
> much harm can be done - and the increase in usability is substantial.
I'm not sure if it's ok create a new cgroup and leave it after the use.
Anyway, none of the existing subcommands create new cgroups
IIUC and I think it'd be ok to print a message on how to create one.
>
> > +static void enter_cgroup(struct cgroup *cgrp)
> > +{
> > + char buf[32];
> > + int fd, len;
> > + pid_t pid;
> > +
> > + if (cgrp == NULL)
> > + return;
> > +
> > + if (threaded)
> > + pid = syscall(__NR_gettid);
> > + else
> > + pid = getpid();
> > +
> > + snprintf(buf, sizeof(buf), "%d\n", pid);
> > + len = strlen(buf);
> > +
> > + /* try cgroup v2 interface first */
> > + if (threaded)
> > + fd = openat(cgrp->fd, "cgroup.threads", O_WRONLY);
> > + else
> > + fd = openat(cgrp->fd, "cgroup.procs", O_WRONLY);
> > +
> > + /* try cgroup v1 if failed */
> > + if (fd < 0)
> > + fd = openat(cgrp->fd, "tasks", O_WRONLY);
> > +
> > + if (fd < 0) {
> > + printf("failed to open cgroup file in %s\n", cgrp->name);
> > + return;
> > + }
> > +
> > + if (write(fd, buf, len) != len)
> > + printf("cannot enter to cgroup: %s\n", cgrp->name);
>
> The failures here should probably result in termination of the run with an
> error code, not just messages which are easy to skip in automated tests?
Right, I'll make the change.
Thanks,
Namhyung
@@ -124,6 +124,14 @@ Options of *pipe*
--loop=::
Specify number of loops.
+-G::
+--cgroups=::
+Names of cgroups for sender and receiver, separated by a comma.
+This is useful to check cgroup context switching overhead.
+Note that perf doesn't create nor delete the cgroups, so users should
+make sure that the cgroups exist and are accessible before use.
+
+
Example of *pipe*
^^^^^^^^^^^^^^^^^
@@ -141,6 +149,17 @@ Example of *pipe*
Total time:0.016 sec
16.948000 usecs/op
59004 ops/sec
+
+% perf bench sched pipe -G AAA,BBB
+(executing 1000000 pipe operations between cgroups)
+# Running 'sched/pipe' benchmark:
+# Executed 1000000 pipe operations between two processes
+
+ Total time: 6.886 [sec]
+
+ 6.886208 usecs/op
+ 145217 ops/sec
+
---------------------
SUITES FOR 'syscall'
@@ -11,6 +11,7 @@
*/
#include <subcmd/parse-options.h>
#include "bench.h"
+#include "util/cgroup.h"
#include <unistd.h>
#include <stdio.h>
@@ -19,6 +20,7 @@
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
+#include <fcntl.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
@@ -40,9 +42,55 @@ static int loops = LOOPS_DEFAULT;
/* Use processes by default: */
static bool threaded;
+static struct cgroup *cgrp_send = NULL;
+static struct cgroup *cgrp_recv = NULL;
+
+static int parse_two_cgroups(const struct option *opt __maybe_unused,
+ const char *str, int unset __maybe_unused)
+{
+ char *p = strdup(str);
+ char *q;
+ int ret = -1;
+
+ if (p == NULL) {
+ fprintf(stderr, "memory allocation failure");
+ return -1;
+ }
+
+ q = strchr(p, ',');
+ if (q == NULL) {
+ fprintf(stderr, "it should have two cgroup names: %s", p);
+ goto out;
+ }
+ *q = '\0';
+
+ cgrp_send = cgroup__new(p, /*do_open=*/true);
+ if (cgrp_send == NULL) {
+ fprintf(stderr, "cannot open sender cgroup: %s", p);
+ goto out;
+ }
+
+ /* skip ',' */
+ q++;
+
+ cgrp_recv = cgroup__new(q, /*do_open=*/true);
+ if (cgrp_recv == NULL) {
+ fprintf(stderr, "cannot open receiver cgroup: %s", q);
+ goto out;
+ }
+ ret = 0;
+
+out:
+ free(p);
+ return ret;
+}
+
static const struct option options[] = {
OPT_INTEGER('l', "loop", &loops, "Specify number of loops"),
OPT_BOOLEAN('T', "threaded", &threaded, "Specify threads/process based task setup"),
+ OPT_CALLBACK('G', "cgroups", NULL, "SEND,RECV",
+ "Put sender and receivers in given cgroups",
+ parse_two_cgroups),
OPT_END()
};
@@ -51,12 +99,54 @@ static const char * const bench_sched_pipe_usage[] = {
NULL
};
+static void enter_cgroup(struct cgroup *cgrp)
+{
+ char buf[32];
+ int fd, len;
+ pid_t pid;
+
+ if (cgrp == NULL)
+ return;
+
+ if (threaded)
+ pid = syscall(__NR_gettid);
+ else
+ pid = getpid();
+
+ snprintf(buf, sizeof(buf), "%d\n", pid);
+ len = strlen(buf);
+
+ /* try cgroup v2 interface first */
+ if (threaded)
+ fd = openat(cgrp->fd, "cgroup.threads", O_WRONLY);
+ else
+ fd = openat(cgrp->fd, "cgroup.procs", O_WRONLY);
+
+ /* try cgroup v1 if failed */
+ if (fd < 0)
+ fd = openat(cgrp->fd, "tasks", O_WRONLY);
+
+ if (fd < 0) {
+ printf("failed to open cgroup file in %s\n", cgrp->name);
+ return;
+ }
+
+ if (write(fd, buf, len) != len)
+ printf("cannot enter to cgroup: %s\n", cgrp->name);
+ close(fd);
+}
+
static void *worker_thread(void *__tdata)
{
struct thread_data *td = __tdata;
int m = 0, i;
int ret;
+ if (td->nr)
+ enter_cgroup(cgrp_send);
+ else
+ enter_cgroup(cgrp_recv);
+
for (i = 0; i < loops; i++) {
if (!td->nr) {
ret = read(td->pipe_read, &m, sizeof(int));
@@ -147,6 +237,9 @@ int bench_sched_pipe(int argc, const char **argv)
gettimeofday(&stop, NULL);
timersub(&stop, &start, &diff);
+ cgroup__put(cgrp_send);
+ cgroup__put(cgrp_recv);
+
switch (bench_format) {
case BENCH_FORMAT_DEFAULT:
printf("# Executed %d pipe operations between two %s\n\n",