[v2] perf bench sched pipe: Add -G/--cgroups option

Message ID 20231013232435.1012585-1-namhyung@kernel.org
State New
Headers
Series [v2] perf bench sched pipe: Add -G/--cgroups option |

Commit Message

Namhyung Kim Oct. 13, 2023, 11:24 p.m. UTC
  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

Ingo Molnar Oct. 14, 2023, 8:44 a.m. UTC | #1
* 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
  
Namhyung Kim Oct. 15, 2023, 6:53 p.m. UTC | #2
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
  

Patch

diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt
index ca5789625cd2..8331bd28b10e 100644
--- a/tools/perf/Documentation/perf-bench.txt
+++ b/tools/perf/Documentation/perf-bench.txt
@@ -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'
diff --git a/tools/perf/bench/sched-pipe.c b/tools/perf/bench/sched-pipe.c
index a960e7a93aec..a98c2e239908 100644
--- a/tools/perf/bench/sched-pipe.c
+++ b/tools/perf/bench/sched-pipe.c
@@ -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",