2018-11-06 15:25:18

by Davidlohr Bueso

[permalink] [raw]
Subject: [PATCH 0/2] perf-bench: introduce epoll benchmarks

Hi,

I've been doing work on epoll, and I've found that most related (micro)benchmarks
are either randomly scattered across the net, or simply non-existent. These patches
are a start to addressing this (more to come). Similar to what we have for futexes,
perf-bench allows kernel developers mainly to quickly test performance changes to the
code in question. Similarly, such programs are not intended for comparing different
IO polling methods. Hence everything is very adhoc and outputs raw microbenchmark
numbers. Also this uses eventfd, similar tools tend to use pipes or sockets, but the
result is the same.

Thanks!

Davidlohr Bueso (2):
perf-bench: Add epoll parallel epoll_wait benchmark
perf-bench: Add epoll_ctl(2) benchmark

tools/perf/Documentation/perf-bench.txt | 10 +
tools/perf/bench/Build | 3 +
tools/perf/bench/bench.h | 3 +
tools/perf/bench/epoll-ctl.c | 411 ++++++++++++++++++++++++
tools/perf/bench/epoll-wait.c | 536 ++++++++++++++++++++++++++++++++
tools/perf/builtin-bench.c | 9 +
6 files changed, 972 insertions(+)
create mode 100644 tools/perf/bench/epoll-ctl.c
create mode 100644 tools/perf/bench/epoll-wait.c

--
2.16.4



2018-11-06 15:24:23

by Davidlohr Bueso

[permalink] [raw]
Subject: [PATCH 2/2] perf-bench: Add epoll_ctl(2) benchmark

Benchmark the various operations allowed for epoll_ctl(2).
The idea is to concurrently stress a single epoll instance
doing add/mod/del operations.

Signed-off-by: Davidlohr Bueso <[email protected]>
---
tools/perf/Documentation/perf-bench.txt | 3 +
tools/perf/bench/Build | 1 +
tools/perf/bench/bench.h | 1 +
tools/perf/bench/epoll-ctl.c | 411 ++++++++++++++++++++++++++++++++
tools/perf/builtin-bench.c | 1 +
5 files changed, 417 insertions(+)
create mode 100644 tools/perf/bench/epoll-ctl.c

diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt
index 3a6b2e73b2e8..0921a3c67381 100644
--- a/tools/perf/Documentation/perf-bench.txt
+++ b/tools/perf/Documentation/perf-bench.txt
@@ -211,6 +211,9 @@ SUITES FOR 'epoll'
*wait*::
Suite for evaluating concurrent epoll_wait calls.

+*ctl*::
+Suite for evaluating multiple epoll_ctl calls.
+
SEE ALSO
--------
linkperf:perf[1]
diff --git a/tools/perf/bench/Build b/tools/perf/bench/Build
index 2bb79b542d53..e4e321b6f883 100644
--- a/tools/perf/bench/Build
+++ b/tools/perf/bench/Build
@@ -8,6 +8,7 @@ perf-y += futex-requeue.o
perf-y += futex-lock-pi.o

perf-y += epoll-wait.o
+perf-y += epoll-ctl.o

perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-lib.o
perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-asm.o
diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h
index c9c276aa6d4e..0e415464931b 100644
--- a/tools/perf/bench/bench.h
+++ b/tools/perf/bench/bench.h
@@ -39,6 +39,7 @@ int bench_futex_requeue(int argc, const char **argv);
int bench_futex_lock_pi(int argc, const char **argv);

int bench_epoll_wait(int argc, const char **argv);
+int bench_epoll_ctl(int argc, const char **argv);

#define BENCH_FORMAT_DEFAULT_STR "default"
#define BENCH_FORMAT_DEFAULT 0
diff --git a/tools/perf/bench/epoll-ctl.c b/tools/perf/bench/epoll-ctl.c
new file mode 100644
index 000000000000..b6f6fc4a7129
--- /dev/null
+++ b/tools/perf/bench/epoll-ctl.c
@@ -0,0 +1,411 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Davidlohr Bueso.
+ *
+ * Benchmark the various operations allowed for epoll_ctl(2).
+ * The idea is to concurrently stress a single epoll instance
+ */
+
+/* For the CLR_() macros */
+#include <string.h>
+#include <pthread.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+
+#include "../util/stat.h"
+#include <subcmd/parse-options.h>
+#include "bench.h"
+#include "cpumap.h"
+
+#include <err.h>
+
+#define printinfo(fmt, arg...) \
+ do { if (__verbose) printf(fmt, ## arg); } while (0)
+
+static unsigned int nthreads = 0;
+static unsigned int nsecs = 8;
+struct timeval start, end, runtime;
+static bool done, __verbose, randomize;
+
+/*
+ * epoll related shared variables.
+ */
+
+/* Maximum number of nesting allowed inside epoll sets */
+#define EPOLL_MAXNESTS 4
+
+enum {
+ OP_EPOLL_ADD,
+ OP_EPOLL_MOD,
+ OP_EPOLL_DEL,
+ EPOLL_NR_OPS,
+};
+
+static int epollfd;
+static int *epollfdp;
+static bool noaffinity;
+static unsigned int nested = 0;
+
+/* amount of fds to monitor, per thread */
+static unsigned int nfds = 64;
+
+static pthread_mutex_t thread_lock;
+static unsigned int threads_starting;
+static struct stats all_stats[EPOLL_NR_OPS];
+static pthread_cond_t thread_parent, thread_worker;
+
+struct worker {
+ int tid;
+ pthread_t thread;
+ unsigned long ops[EPOLL_NR_OPS];
+ int *fdmap;
+};
+
+static const struct option options[] = {
+ OPT_UINTEGER('t', "threads", &nthreads, "Specify amount of threads"),
+ OPT_UINTEGER('r', "runtime", &nsecs, "Specify runtime (in seconds)"),
+ OPT_UINTEGER('f', "nfds", &nfds, "Specify amount of file descriptors to monitor for each thread"),
+ OPT_BOOLEAN( 'n', "noaffinity", &noaffinity, "Disables CPU affinity"),
+ OPT_UINTEGER( 'N', "nested", &nested, "Nesting level epoll hierarchy (default is 0, no nesting)"),
+ OPT_BOOLEAN( 'R', "randomize", &randomize, "Perform random operations on random fds"),
+ OPT_BOOLEAN( 'v', "verbose", &__verbose, "Verbose mode"),
+ OPT_END()
+};
+
+static const char * const bench_epoll_ctl_usage[] = {
+ "perf bench epoll ctl <options>",
+ NULL
+};
+
+static void toggle_done(int sig __maybe_unused,
+ siginfo_t *info __maybe_unused,
+ void *uc __maybe_unused)
+{
+ /* inform all threads that we're done for the day */
+ done = true;
+ gettimeofday(&end, NULL);
+ timersub(&end, &start, &runtime);
+}
+
+static void nest_epollfd(void)
+{
+ unsigned int i;
+ struct epoll_event ev;
+
+ if (nested > EPOLL_MAXNESTS)
+ nested = EPOLL_MAXNESTS;
+ printinfo("Nesting level(s): %d\n", nested);
+
+ epollfdp = calloc(nested, sizeof(int));
+ if (!epollfd)
+ err(EXIT_FAILURE, "calloc");
+
+ for (i = 0; i < nested; i++) {
+ epollfdp[i] = epoll_create(1);
+ if (epollfd < 0)
+ err(EXIT_FAILURE, "epoll_create");
+ }
+
+ ev.events = EPOLLHUP; /* anything */
+ ev.data.u64 = i; /* any number */
+
+ for (i = nested - 1; i; i--) {
+ if (epoll_ctl(epollfdp[i - 1], EPOLL_CTL_ADD,
+ epollfdp[i], &ev) < 0)
+ err(EXIT_FAILURE, "epoll_ctl");
+ }
+
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, *epollfdp, &ev) < 0)
+ err(EXIT_FAILURE, "epoll_ctl");
+}
+
+static inline void do_epoll_op(struct worker *w, int op, int fd)
+{
+ int error;
+ struct epoll_event ev;
+
+ ev.events = EPOLLIN;
+ ev.data.u64 = fd;
+
+ switch (op) {
+ case OP_EPOLL_ADD:
+ error = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
+ break;
+ case OP_EPOLL_MOD:
+ ev.events = EPOLLOUT;
+ error = epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
+ break;
+ case OP_EPOLL_DEL:
+ error = epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
+ break;
+ default:
+ error = 1;
+ break;
+ }
+
+ if (!error)
+ w->ops[op]++;
+}
+
+static inline void do_random_epoll_op(struct worker *w)
+{
+ unsigned long rnd1 = random(), rnd2 = random();
+ int op, fd;
+
+ fd = w->fdmap[rnd1 % nfds];
+ op = rnd2 % EPOLL_NR_OPS;
+
+ do_epoll_op(w, op, fd);
+}
+
+static void *workerfn(void *arg)
+{
+ unsigned int i;
+ struct worker *w = (struct worker *) arg;
+ struct timespec ts = { .tv_sec = 0,
+ .tv_nsec = 250 };
+
+ pthread_mutex_lock(&thread_lock);
+ threads_starting--;
+ if (!threads_starting)
+ pthread_cond_signal(&thread_parent);
+ pthread_cond_wait(&thread_worker, &thread_lock);
+ pthread_mutex_unlock(&thread_lock);
+
+ /* Let 'em loose */
+ do {
+ /* random */
+ if (randomize) {
+ do_random_epoll_op(w);
+ } else {
+ for (i = 0; i < nfds; i++) {
+ do_epoll_op(w, OP_EPOLL_ADD, w->fdmap[i]);
+ do_epoll_op(w, OP_EPOLL_MOD, w->fdmap[i]);
+ do_epoll_op(w, OP_EPOLL_DEL, w->fdmap[i]);
+ }
+ }
+
+ nanosleep(&ts, NULL);
+ } while (!done);
+
+ return NULL;
+}
+
+static void init_fdmaps(struct worker *w, int pct)
+{
+ ssize_t i;
+ int inc;
+ struct epoll_event ev;
+
+ if (!pct)
+ return;
+
+ inc = 100/pct;
+ for (i = 0; i < nfds; i+=inc) {
+ ev.data.fd = w->fdmap[i];
+ ev.events = EPOLLIN;
+
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, w->fdmap[i], &ev) < 0)
+ err(EXIT_FAILURE, "epoll_ct");
+ }
+}
+
+static int do_threads(struct worker *worker, struct cpu_map *cpu)
+{
+ pthread_attr_t thread_attr, *attrp = NULL;
+ cpu_set_t cpuset;
+ unsigned int i, j;
+ int ret;
+
+ if (!noaffinity)
+ pthread_attr_init(&thread_attr);
+
+ for (i = 0; i < nthreads; i++) {
+ struct worker *w = &worker[i];
+
+ w->tid = i;
+ w->fdmap = calloc(nfds, sizeof(int));
+ if (!w->fdmap)
+ return 1;
+
+ for (j = 0; j < nfds; j++) {
+ w->fdmap[j] = eventfd(0, EFD_NONBLOCK);
+ if (w->fdmap[j] < 0)
+ err(EXIT_FAILURE, "eventfd");
+ }
+
+ /*
+ * Lets add 50% of the fdmap to the epoll instance, and
+ * do it before any threads are started; otherwise there is
+ * an initial bias of the call failing (mod and del ops).
+ */
+ if (randomize)
+ init_fdmaps(w, 50);
+
+ if (!noaffinity) {
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpu->map[i % cpu->nr], &cpuset);
+
+ ret = pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpuset);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_attr_setaffinity_np");
+
+ attrp = &thread_attr;
+ }
+
+ ret = pthread_create(&w->thread, attrp, workerfn,
+ (void *)(struct worker *) w);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_create");
+ }
+
+ if (!noaffinity)
+ pthread_attr_destroy(&thread_attr);
+
+ return ret;
+}
+
+static void print_summary(void)
+{
+ int i;
+ unsigned long avg[EPOLL_NR_OPS];
+ double stddev[EPOLL_NR_OPS];
+
+ for (i = 0; i < EPOLL_NR_OPS; i++) {
+ avg[i] = avg_stats(&all_stats[i]);
+ stddev[i] = stddev_stats(&all_stats[i]);
+ }
+
+ printf("\nAveraged %ld ADD operations (+- %.2f%%)\n",
+ avg[OP_EPOLL_ADD], rel_stddev_stats(stddev[OP_EPOLL_ADD],
+ avg[OP_EPOLL_ADD]));
+ printf("Averaged %ld MOD operations (+- %.2f%%)\n",
+ avg[OP_EPOLL_MOD], rel_stddev_stats(stddev[OP_EPOLL_MOD],
+ avg[OP_EPOLL_MOD]));
+ printf("Averaged %ld DEL operations (+- %.2f%%)\n",
+ avg[OP_EPOLL_DEL], rel_stddev_stats(stddev[OP_EPOLL_DEL],
+ avg[OP_EPOLL_DEL]));
+}
+
+int bench_epoll_ctl(int argc, const char **argv)
+{
+ int j, ret = 0;
+ struct sigaction act;
+ struct worker *worker = NULL;
+ struct cpu_map *cpu;
+ struct rlimit rl, prevrl;
+ ssize_t i;
+
+ argc = parse_options(argc, argv, options, bench_epoll_ctl_usage, 0);
+ if (argc) {
+ usage_with_options(bench_epoll_ctl_usage, options);
+ exit(EXIT_FAILURE);
+ }
+
+ sigfillset(&act.sa_mask);
+ act.sa_sigaction = toggle_done;
+ sigaction(SIGINT, &act, NULL);
+
+ cpu = cpu_map__new(NULL);
+ if (!cpu)
+ goto errmem;
+
+ /* a single, main epoll instance */
+ epollfd = epoll_create(1);
+ if (epollfd < 0)
+ err(EXIT_FAILURE, "epoll_create");
+
+ /*
+ * Deal with nested epolls, if any.
+ */
+ if (nested)
+ nest_epollfd();
+
+ /* default to the number of CPUs */
+ if (!nthreads)
+ nthreads = cpu->nr;
+
+ worker = calloc(nthreads, sizeof(*worker));
+ if (!worker)
+ goto errmem;
+
+ if (getrlimit(RLIMIT_NOFILE, &prevrl))
+ err(EXIT_FAILURE, "getrlimit");
+ rl.rlim_cur = rl.rlim_max = nfds * nthreads * 2 + 50;
+ printinfo("Setting RLIMIT_NOFILE rlimit from %lu to: %lu\n",
+ prevrl.rlim_max, rl.rlim_max);
+ if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
+ err(EXIT_FAILURE, "setrlimit");
+
+ printf("Run summary [PID %d]: %d threads doing epoll_ctl ops "
+ "%d file-descriptors for %d secs.\n\n",
+ getpid(), nthreads, nfds, nsecs);
+
+ for (i = 0; i < EPOLL_NR_OPS; i++)
+ init_stats(&all_stats[i]);
+
+ pthread_mutex_init(&thread_lock, NULL);
+ pthread_cond_init(&thread_parent, NULL);
+ pthread_cond_init(&thread_worker, NULL);
+
+ threads_starting = nthreads;
+
+ gettimeofday(&start, NULL);
+
+ do_threads(worker, cpu);
+
+ pthread_mutex_lock(&thread_lock);
+ while (threads_starting)
+ pthread_cond_wait(&thread_parent, &thread_lock);
+ pthread_cond_broadcast(&thread_worker);
+ pthread_mutex_unlock(&thread_lock);
+
+ sleep(nsecs);
+ toggle_done(0, NULL, NULL);
+ printinfo("main thread: toggling done\n");
+
+ for (i = 0; i < nthreads; i++) {
+ ret = pthread_join(worker[i].thread, NULL);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_join");
+ }
+
+ /* cleanup & report results */
+ pthread_cond_destroy(&thread_parent);
+ pthread_cond_destroy(&thread_worker);
+ pthread_mutex_destroy(&thread_lock);
+
+ for (i = 0; i < nthreads; i++) {
+ unsigned long t[EPOLL_NR_OPS];
+
+ for (j = 0; j < EPOLL_NR_OPS; j++) {
+ t[j] = worker[i].ops[j];
+ update_stats(&all_stats[j], t[j]);
+ }
+
+ if (nfds == 1)
+ printf("[thread %2d] fdmap: %p [ add: %04ld; mod: %04ld; del: %04lds ops ]\n",
+ worker[i].tid, &worker[i].fdmap[0],
+ t[OP_EPOLL_ADD], t[OP_EPOLL_MOD], t[OP_EPOLL_DEL]);
+ else
+ printf("[thread %2d] fdmap: %p ... %p [ add: %04ld ops; mod: %04ld ops; del: %04ld ops ]\n",
+ worker[i].tid, &worker[i].fdmap[0],
+ &worker[i].fdmap[nfds-1],
+ t[OP_EPOLL_ADD], t[OP_EPOLL_MOD], t[OP_EPOLL_DEL]);
+ }
+
+ print_summary();
+
+ close(epollfd);
+ return ret;
+errmem:
+ err(EXIT_FAILURE, "calloc");
+}
diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c
index aeb92e531b7c..ca1d8f52173a 100644
--- a/tools/perf/builtin-bench.c
+++ b/tools/perf/builtin-bench.c
@@ -70,6 +70,7 @@ static struct bench futex_benchmarks[] = {

static struct bench epoll_benchmarks[] = {
{ "wait", "Benchmark epoll concurrent epoll_waits", bench_epoll_wait },
+ { "ctl", "Benchmark epoll concurrent epoll_ctls", bench_epoll_ctl },
{ "all", "Run all futex benchmarks", NULL },
{ NULL, NULL, NULL }
};
--
2.16.4


2018-11-06 15:25:55

by Davidlohr Bueso

[permalink] [raw]
Subject: [PATCH 1/2] perf-bench: Add epoll parallel epoll_wait benchmark

This program benchmarks concurrent epoll_wait(2) for file
descriptors that are monitored with with EPOLLIN along various
semantics, by a single epoll instance. Such conditions can be
found when using single/combined or multiple queuing when load
balancing.

Each thread has a number of private, nonblocking file descriptors,
referred to as fdmap. A writer thread will constantly be writing to
the fdmaps of all threads, minimizing each threads's chances of
epoll_wait not finding any ready read events and blocking as this
is not what we want to stress. Full details in the start of the
C file.

Signed-off-by: Davidlohr Bueso <[email protected]>
---
tools/perf/Documentation/perf-bench.txt | 7 +
tools/perf/bench/Build | 2 +
tools/perf/bench/bench.h | 2 +
tools/perf/bench/epoll-wait.c | 536 ++++++++++++++++++++++++++++++++
tools/perf/builtin-bench.c | 8 +
5 files changed, 555 insertions(+)
create mode 100644 tools/perf/bench/epoll-wait.c

diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt
index 34750fc32714..3a6b2e73b2e8 100644
--- a/tools/perf/Documentation/perf-bench.txt
+++ b/tools/perf/Documentation/perf-bench.txt
@@ -58,6 +58,9 @@ SUBSYSTEM
'futex'::
Futex stressing benchmarks.

+'epoll'::
+ Eventpoll (epoll) stressing benchmarks.
+
'all'::
All benchmark subsystems.

@@ -203,6 +206,10 @@ Suite for evaluating requeue calls.
*lock-pi*::
Suite for evaluating futex lock_pi calls.

+SUITES FOR 'epoll'
+~~~~~~~~~~~~~~~~~~
+*wait*::
+Suite for evaluating concurrent epoll_wait calls.

SEE ALSO
--------
diff --git a/tools/perf/bench/Build b/tools/perf/bench/Build
index eafce1a130a1..2bb79b542d53 100644
--- a/tools/perf/bench/Build
+++ b/tools/perf/bench/Build
@@ -7,6 +7,8 @@ perf-y += futex-wake-parallel.o
perf-y += futex-requeue.o
perf-y += futex-lock-pi.o

+perf-y += epoll-wait.o
+
perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-lib.o
perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-asm.o
perf-$(CONFIG_X86_64) += mem-memset-x86-64-asm.o
diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h
index 6c9fcd757f31..c9c276aa6d4e 100644
--- a/tools/perf/bench/bench.h
+++ b/tools/perf/bench/bench.h
@@ -38,6 +38,8 @@ int bench_futex_requeue(int argc, const char **argv);
/* pi futexes */
int bench_futex_lock_pi(int argc, const char **argv);

+int bench_epoll_wait(int argc, const char **argv);
+
#define BENCH_FORMAT_DEFAULT_STR "default"
#define BENCH_FORMAT_DEFAULT 0
#define BENCH_FORMAT_SIMPLE_STR "simple"
diff --git a/tools/perf/bench/epoll-wait.c b/tools/perf/bench/epoll-wait.c
new file mode 100644
index 000000000000..c4c5ef60feb4
--- /dev/null
+++ b/tools/perf/bench/epoll-wait.c
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Davidlohr Bueso.
+ *
+ * This program benchmarks concurrent epoll_wait(2) monitoring multiple
+ * file descriptors under one or two load balancing models. The first,
+ * and default, is the single/combined queueing (which refers to a single
+ * epoll instance for N worker threads):
+ *
+ * |---> [worker A]
+ * |---> [worker B]
+ * [combined queue] .---> [worker C]
+ * |---> [worker D]
+ * |---> [worker E]
+ *
+ * While the second model, enabled via --multiq option, uses multiple
+ * queueing (which refers to one epoll instance per worker). For example,
+ * short lived tcp connections in a high throughput httpd server will
+ * ditribute the accept()'ing connections across CPUs. In this case each
+ * worker does a limited amount of processing.
+ *
+ * [queue A] ---> [worker]
+ * [queue B] ---> [worker]
+ * [queue C] ---> [worker]
+ * [queue D] ---> [worker]
+ * [queue E] ---> [worker]
+ *
+ * Naturally, the single queue will enforce more concurrency on the epoll
+ * instance, and can therefore scale poorly compared to multiple queues.
+ * However, this is a benchmark raw data and must be taken with a grain of
+ * salt when choosing how to make use of sys_epoll.
+
+ * Each thread has a number of private, nonblocking file descriptors,
+ * referred to as fdmap. A writer thread will constantly be writing to
+ * the fdmaps of all threads, minimizing each threads's chances of
+ * epoll_wait not finding any ready read events and blocking as this
+ * is not what we want to stress. The size of the fdmap can be adjusted
+ * by the user; enlarging the value will increase the chances of
+ * epoll_wait(2) blocking as the lineal writer thread will take "longer",
+ * at least at a high level.
+ *
+ * Note that because fds are private to each thread, this workload does
+ * not stress scenarios where multiple tasks are awoken per ready IO; ie:
+ * EPOLLEXCLUSIVE semantics.
+ *
+ * The end result/metric is throughput: number of ops/second where an
+ * operation consists of:
+ *
+ * epoll_wait(2) + [others]
+ *
+ * ... where [others] is the cost of re-adding the fd (EPOLLET),
+ * or rearming it (EPOLLONESHOT).
+ *
+ *
+ * The purpose of this is program is that it be useful for measuring
+ * kernel related changes to the sys_epoll, and not comparing different
+ * IO polling methods, for example. Hence everything is very adhoc and
+ * outputs raw microbenchmark numbers. Also this uses eventfd, similar
+ * tools tend to use pipes or sockets, but the result is the same.
+ */
+
+/* For the CLR_() macros */
+#include <string.h>
+#include <pthread.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <sys/types.h>
+
+#include "../util/stat.h"
+#include <subcmd/parse-options.h>
+#include "bench.h"
+#include "cpumap.h"
+
+#include <err.h>
+
+#define printinfo(fmt, arg...) \
+ do { if (__verbose) { printf(fmt, ## arg); fflush(stdout); } } while (0)
+
+static unsigned int nthreads = 0;
+static unsigned int nsecs = 8;
+struct timeval start, end, runtime;
+static bool wdone, done, __verbose, randomize, nonblocking;
+
+/*
+ * epoll related shared variables.
+ */
+
+/* Maximum number of nesting allowed inside epoll sets */
+#define EPOLL_MAXNESTS 4
+
+static int epollfd;
+static int *epollfdp;
+static bool noaffinity;
+static unsigned int nested = 0;
+static bool et; /* edge-trigger */
+static bool oneshot;
+static bool multiq; /* use an epoll instance per thread */
+
+/* amount of fds to monitor, per thread */
+static unsigned int nfds = 64;
+
+static pthread_mutex_t thread_lock;
+static unsigned int threads_starting;
+static struct stats throughput_stats;
+static pthread_cond_t thread_parent, thread_worker;
+
+struct worker {
+ int tid;
+ int epollfd; /* for --multiq */
+ pthread_t thread;
+ unsigned long ops;
+ int *fdmap;
+};
+
+static const struct option options[] = {
+ /* general benchmark options */
+ OPT_UINTEGER('t', "threads", &nthreads, "Specify amount of threads"),
+ OPT_UINTEGER('r', "runtime", &nsecs, "Specify runtime (in seconds)"),
+ OPT_UINTEGER('f', "nfds", &nfds, "Specify amount of file descriptors to monitor for each thread"),
+ OPT_BOOLEAN( 'n', "noaffinity", &noaffinity, "Disables CPU affinity"),
+ OPT_BOOLEAN('R', "randomize", &randomize, "Enable random write behaviour (default is lineal)"),
+ OPT_BOOLEAN( 'v', "verbose", &__verbose, "Verbose mode"),
+
+ /* epoll specific options */
+ OPT_BOOLEAN( 'm', "multiq", &multiq, "Use multiple epoll instances (one per thread)"),
+ OPT_BOOLEAN( 'B', "nonblocking", &nonblocking, "Nonblocking epoll_wait(2) behaviour"),
+ OPT_UINTEGER( 'N', "nested", &nested, "Nesting level epoll hierarchy (default is 0, no nesting)"),
+ OPT_BOOLEAN( 'S', "oneshot", &oneshot, "Use EPOLLONESHOT semantics"),
+ OPT_BOOLEAN( 'E', "edge", &et, "Use Edge-triggered interface (default is LT)"),
+
+ OPT_END()
+};
+
+static const char * const bench_epoll_wait_usage[] = {
+ "perf bench epoll wait <options>",
+ NULL
+};
+
+
+/*
+ * Arrange the N elements of ARRAY in random order.
+ * Only effective if N is much smaller than RAND_MAX;
+ * if this may not be the case, use a better random
+ * number generator. -- Ben Pfaff.
+ */
+static void shuffle(void *array, size_t n, size_t size)
+{
+ char *carray = array;
+ void *aux;
+ size_t i;
+
+ if (n <= 1)
+ return;
+
+ aux = calloc(1, size);
+ if (!aux)
+ err(EXIT_FAILURE, "calloc");
+
+ for (i = 1; i < n; ++i) {
+ size_t j = i + rand() / (RAND_MAX / (n - i) + 1);
+ j *= size;
+
+ memcpy(aux, &carray[j], size);
+ memcpy(&carray[j], &carray[i*size], size);
+ memcpy(&carray[i*size], aux, size);
+ }
+
+ free(aux);
+}
+
+
+static void *workerfn(void *arg)
+{
+ int fd, ret, r;
+ struct worker *w = (struct worker *) arg;
+ unsigned long ops = w->ops;
+ struct epoll_event ev;
+ uint64_t val;
+ int to = nonblocking? 0 : -1;
+ int efd = multiq ? w->epollfd : epollfd;
+
+ pthread_mutex_lock(&thread_lock);
+ threads_starting--;
+ if (!threads_starting)
+ pthread_cond_signal(&thread_parent);
+ pthread_cond_wait(&thread_worker, &thread_lock);
+ pthread_mutex_unlock(&thread_lock);
+
+ do {
+ /*
+ * Block undefinitely waiting for the IN event.
+ * In order to stress the epoll_wait(2) syscall,
+ * call it event per event, instead of a larger
+ * batch (max)limit.
+ */
+ do {
+ ret = epoll_wait(efd, &ev, 1, to);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0)
+ err(EXIT_FAILURE, "epoll_wait");
+
+ fd = ev.data.fd;
+
+ do {
+ r = read(fd, &val, sizeof(val));
+ } while (!done && (r < 0 && errno == EAGAIN));
+
+ if (et) {
+ ev.events = EPOLLIN | EPOLLET;
+ ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
+ }
+
+ if (oneshot) {
+ /* rearm the file descriptor with a new event mask */
+ ev.events |= EPOLLIN | EPOLLONESHOT;
+ ret = epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
+ }
+
+ ops++;
+ } while (!done);
+
+ if (multiq)
+ close(w->epollfd);
+
+ w->ops = ops;
+ return NULL;
+}
+
+static void nest_epollfd(struct worker *w)
+{
+ unsigned int i;
+ struct epoll_event ev;
+ int efd = multiq ? w->epollfd : epollfd;
+
+ if (nested > EPOLL_MAXNESTS)
+ nested = EPOLL_MAXNESTS;
+
+ epollfdp = calloc(nested, sizeof(*epollfdp));
+ if (!epollfdp)
+ err(EXIT_FAILURE, "calloc");
+
+ for (i = 0; i < nested; i++) {
+ epollfdp[i] = epoll_create(1);
+ if (epollfd < 0)
+ err(EXIT_FAILURE, "epoll_create");
+ }
+
+ ev.events = EPOLLHUP; /* anything */
+ ev.data.u64 = i; /* any number */
+
+ for (i = nested - 1; i; i--) {
+ if (epoll_ctl(epollfdp[i - 1], EPOLL_CTL_ADD,
+ epollfdp[i], &ev) < 0)
+ err(EXIT_FAILURE, "epoll_ctl");
+ }
+
+ if (epoll_ctl(efd, EPOLL_CTL_ADD, *epollfdp, &ev) < 0)
+ err(EXIT_FAILURE, "epoll_ctl");
+}
+
+static void toggle_done(int sig __maybe_unused,
+ siginfo_t *info __maybe_unused,
+ void *uc __maybe_unused)
+{
+ /* inform all threads that we're done for the day */
+ done = true;
+ gettimeofday(&end, NULL);
+ timersub(&end, &start, &runtime);
+}
+
+static void print_summary(void)
+{
+ unsigned long avg = avg_stats(&throughput_stats);
+ double stddev = stddev_stats(&throughput_stats);
+
+ printf("\nAveraged %ld operations/sec (+- %.2f%%), total secs = %d\n",
+ avg, rel_stddev_stats(stddev, avg),
+ (int) runtime.tv_sec);
+}
+
+static int do_threads(struct worker *worker, struct cpu_map *cpu)
+{
+ pthread_attr_t thread_attr, *attrp = NULL;
+ cpu_set_t cpuset;
+ unsigned int i, j;
+ int ret, events = EPOLLIN;
+
+ if (oneshot)
+ events |= EPOLLONESHOT;
+ if (et)
+ events |= EPOLLET;
+
+ printinfo("starting worker/consumer %sthreads%s\n",
+ noaffinity ? "":"CPU affinity ",
+ nonblocking ? " (nonblocking)":"");
+ if (!noaffinity)
+ pthread_attr_init(&thread_attr);
+
+ for (i = 0; i < nthreads; i++) {
+ struct worker *w = &worker[i];
+
+ if (multiq) {
+ w->epollfd = epoll_create(1);
+ if (w->epollfd < 0)
+ err(EXIT_FAILURE, "epoll_create");
+
+ if (nested)
+ nest_epollfd(w);
+ }
+
+ w->tid = i;
+ w->fdmap = calloc(nfds, sizeof(int));
+ if (!w->fdmap)
+ return 1;
+
+ for (j = 0; j < nfds; j++) {
+ int efd = multiq ? w->epollfd : epollfd;
+ struct epoll_event ev;
+
+ w->fdmap[j] = eventfd(0, EFD_NONBLOCK);
+ if (w->fdmap[j] < 0)
+ err(EXIT_FAILURE, "eventfd");
+
+ ev.data.fd = w->fdmap[j];
+ ev.events = events;
+
+ ret = epoll_ctl(efd, EPOLL_CTL_ADD,
+ w->fdmap[j], &ev);
+ if (ret < 0)
+ err(EXIT_FAILURE, "epoll_ctl");
+ }
+
+ if (!noaffinity) {
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpu->map[i % cpu->nr], &cpuset);
+
+ ret = pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpuset);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_attr_setaffinity_np");
+
+ attrp = &thread_attr;
+ }
+
+ ret = pthread_create(&w->thread, attrp, workerfn,
+ (void *)(struct worker *) w);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_create");
+ }
+
+ if (!noaffinity)
+ pthread_attr_destroy(&thread_attr);
+
+ return ret;
+}
+
+static void *writerfn(void *p)
+{
+ struct worker *worker = p;
+ size_t i, j, iter;
+ const uint64_t val = 1;
+ ssize_t sz;
+ struct timespec ts = { .tv_sec = 0,
+ .tv_nsec = 500 };
+
+ printinfo("starting writer-thread: doing %s writes ...\n",
+ randomize? "random":"lineal");
+
+ for (iter = 0; !wdone; iter++) {
+ if (randomize) {
+ shuffle((void *)worker, nthreads, sizeof(*worker));
+ }
+
+ for (i = 0; i < nthreads; i++) {
+ struct worker *w = &worker[i];
+
+ if (randomize) {
+ shuffle((void *)w->fdmap, nfds, sizeof(int));
+ }
+
+ for (j = 0; j < nfds; j++) {
+ do {
+ sz = write(w->fdmap[j], &val, sizeof(val));
+ } while (!wdone && (sz < 0 && errno == EAGAIN));
+ }
+ }
+
+ nanosleep(&ts, NULL);
+ }
+
+ printinfo("exiting writer-thread (total full-loops: %ld)\n", iter);
+ return NULL;
+}
+
+static int cmpworker(const void *p1, const void *p2)
+{
+
+ struct worker *w1 = (struct worker *) p1;
+ struct worker *w2 = (struct worker *) p2;
+ return w1->tid > w2->tid;
+}
+
+int bench_epoll_wait(int argc, const char **argv)
+{
+ int ret = 0;
+ struct sigaction act;
+ unsigned int i;
+ struct worker *worker = NULL;
+ struct cpu_map *cpu;
+ pthread_t wthread;
+ struct rlimit rl, prevrl;
+
+ argc = parse_options(argc, argv, options, bench_epoll_wait_usage, 0);
+ if (argc) {
+ usage_with_options(bench_epoll_wait_usage, options);
+ exit(EXIT_FAILURE);
+ }
+
+ sigfillset(&act.sa_mask);
+ act.sa_sigaction = toggle_done;
+ sigaction(SIGINT, &act, NULL);
+
+ cpu = cpu_map__new(NULL);
+ if (!cpu)
+ goto errmem;
+
+ /* a single, main epoll instance */
+ if (!multiq) {
+ epollfd = epoll_create(1);
+ if (epollfd < 0)
+ err(EXIT_FAILURE, "epoll_create");
+
+ /*
+ * Deal with nested epolls, if any.
+ */
+ if (nested)
+ nest_epollfd(NULL);
+ }
+
+ printinfo("Using %s queue model\n", multiq ? "multi" : "single");
+ printinfo("Nesting level(s): %d\n", nested);
+
+ /* default to the number of CPUs and leave one for the writer pthread */
+ if (!nthreads)
+ nthreads = cpu->nr - 1;
+
+ worker = calloc(nthreads, sizeof(*worker));
+ if (!worker) {
+ goto errmem;
+ }
+
+ if (getrlimit(RLIMIT_NOFILE, &prevrl))
+ err(EXIT_FAILURE, "getrlimit");
+ rl.rlim_cur = rl.rlim_max = nfds * nthreads * 2 + 50;
+ printinfo("Setting RLIMIT_NOFILE rlimit from %lu to: %lu\n", prevrl.rlim_max, rl.rlim_max);
+ if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
+ err(EXIT_FAILURE, "setrlimit");
+
+ printf("Run summary [PID %d]: %d threads monitoring%s on "
+ "%d file-descriptors for %d secs.\n\n",
+ getpid(), nthreads, oneshot ? " (EPOLLONESHOT semantics)": "", nfds, nsecs);
+
+ init_stats(&throughput_stats);
+ pthread_mutex_init(&thread_lock, NULL);
+ pthread_cond_init(&thread_parent, NULL);
+ pthread_cond_init(&thread_worker, NULL);
+
+ threads_starting = nthreads;
+
+ gettimeofday(&start, NULL);
+
+ do_threads(worker, cpu);
+
+ pthread_mutex_lock(&thread_lock);
+ while (threads_starting)
+ pthread_cond_wait(&thread_parent, &thread_lock);
+ pthread_cond_broadcast(&thread_worker);
+ pthread_mutex_unlock(&thread_lock);
+
+ /*
+ * At this point the workers should be blocked waiting for read events
+ * to become ready. Launch the writer which will constantly be writing
+ * to each thread's fdmap.
+ */
+ ret = pthread_create(&wthread, NULL, writerfn,
+ (void *)(struct worker *) worker);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_create");
+
+ sleep(nsecs);
+ toggle_done(0, NULL, NULL);
+ printinfo("main thread: toggling done\n");
+
+ sleep(1); /* meh */
+ wdone = true;
+ ret = pthread_join(wthread, NULL);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_join");
+
+ /* cleanup & report results */
+ pthread_cond_destroy(&thread_parent);
+ pthread_cond_destroy(&thread_worker);
+ pthread_mutex_destroy(&thread_lock);
+
+ /* sort the array back before reporting */
+ if (randomize)
+ qsort(worker, nthreads, sizeof(struct worker), cmpworker);
+
+ for (i = 0; i < nthreads; i++) {
+ unsigned long t = worker[i].ops/runtime.tv_sec;
+
+ update_stats(&throughput_stats, t);
+
+ if (nfds == 1)
+ printf("[thread %2d] fdmap: %p [ %04ld ops/sec ]\n",
+ worker[i].tid, &worker[i].fdmap[0], t);
+ else
+ printf("[thread %2d] fdmap: %p ... %p [ %04ld ops/sec ]\n",
+ worker[i].tid, &worker[i].fdmap[0],
+ &worker[i].fdmap[nfds-1], t);
+ }
+
+ print_summary();
+
+ close(epollfd);
+ return ret;
+errmem:
+ err(EXIT_FAILURE, "calloc");
+}
diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c
index 17a6bcd01aa6..aeb92e531b7c 100644
--- a/tools/perf/builtin-bench.c
+++ b/tools/perf/builtin-bench.c
@@ -14,6 +14,7 @@
* mem ... memory access performance
* numa ... NUMA scheduling and MM performance
* futex ... Futex performance
+ * epoll ... Event poll performance
*/
#include "perf.h"
#include "util/util.h"
@@ -67,6 +68,12 @@ static struct bench futex_benchmarks[] = {
{ NULL, NULL, NULL }
};

+static struct bench epoll_benchmarks[] = {
+ { "wait", "Benchmark epoll concurrent epoll_waits", bench_epoll_wait },
+ { "all", "Run all futex benchmarks", NULL },
+ { NULL, NULL, NULL }
+};
+
struct collection {
const char *name;
const char *summary;
@@ -80,6 +87,7 @@ static struct collection collections[] = {
{ "numa", "NUMA scheduling and MM benchmarks", numa_benchmarks },
#endif
{"futex", "Futex stressing benchmarks", futex_benchmarks },
+ {"epoll", "Epoll stressing benchmarks", epoll_benchmarks },
{ "all", "All benchmarks", NULL },
{ NULL, NULL, NULL }
};
--
2.16.4


2018-11-06 18:42:13

by Davidlohr Bueso

[permalink] [raw]
Subject: Re: [PATCH 1/2] perf-bench: Add epoll parallel epoll_wait benchmark

Mind this fixlet for using et/oneshot and the multiq option.

diff --git a/tools/perf/bench/epoll-wait.c b/tools/perf/bench/epoll-wait.c
index c4c5ef60feb4..4e4efc5cfe22 100644
--- a/tools/perf/bench/epoll-wait.c
+++ b/tools/perf/bench/epoll-wait.c
@@ -215,13 +215,13 @@ static void *workerfn(void *arg)

if (et) {
ev.events = EPOLLIN | EPOLLET;
- ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
+ ret = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev);
}

if (oneshot) {
/* rearm the file descriptor with a new event mask */
ev.events |= EPOLLIN | EPOLLONESHOT;
- ret = epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
+ ret = epoll_ctl(efd, EPOLL_CTL_MOD, fd, &ev);
}

ops++;
@@ -249,7 +249,7 @@ static void nest_epollfd(struct worker *w)

for (i = 0; i < nested; i++) {
epollfdp[i] = epoll_create(1);
- if (epollfd < 0)
+ if (epollfdp[i] < 0)
err(EXIT_FAILURE, "epoll_create");
}


2018-11-06 19:14:37

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH 1/2] perf-bench: Add epoll parallel epoll_wait benchmark

Em Tue, Nov 06, 2018 at 10:23:49AM -0800, Davidlohr Bueso escreveu:
> Mind this fixlet for using et/oneshot and the multiq option.

Yes sir, applied the fixup to the first patch in the series, applied the
second, tested, merged.

- Arnaldo

> diff --git a/tools/perf/bench/epoll-wait.c b/tools/perf/bench/epoll-wait.c
> index c4c5ef60feb4..4e4efc5cfe22 100644
> --- a/tools/perf/bench/epoll-wait.c
> +++ b/tools/perf/bench/epoll-wait.c
> @@ -215,13 +215,13 @@ static void *workerfn(void *arg)
>
> if (et) {
> ev.events = EPOLLIN | EPOLLET;
> - ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
> + ret = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev);
> }
>
> if (oneshot) {
> /* rearm the file descriptor with a new event mask */
> ev.events |= EPOLLIN | EPOLLONESHOT;
> - ret = epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
> + ret = epoll_ctl(efd, EPOLL_CTL_MOD, fd, &ev);
> }
>
> ops++;
> @@ -249,7 +249,7 @@ static void nest_epollfd(struct worker *w)
>
> for (i = 0; i < nested; i++) {
> epollfdp[i] = epoll_create(1);
> - if (epollfd < 0)
> + if (epollfdp[i] < 0)
> err(EXIT_FAILURE, "epoll_create");
> }
>

2018-11-08 13:46:28

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH 1/2] perf-bench: Add epoll parallel epoll_wait benchmark

Em Tue, Nov 06, 2018 at 04:13:25PM -0300, Arnaldo Carvalho de Melo escreveu:
> Em Tue, Nov 06, 2018 at 10:23:49AM -0800, Davidlohr Bueso escreveu:
> > Mind this fixlet for using et/oneshot and the multiq option.
>
> Yes sir, applied the fixup to the first patch in the series, applied the
> second, tested, merged.

So, one of these two is making the build to fail in all the Alpine Linux
(3.4 -- 3.8 and edge) and also on some others

On Alpine the error is:

CC /tmp/build/perf/bench/futex-lock-pi.o
CC /tmp/build/perf/bench/epoll-wait.o
bench/epoll-wait.c: In function 'do_threads':
bench/epoll-wait.c:345:10: error: implicit declaration of function 'pthread_attr_setaffinity_np' [-Werror=implicit-function-declaration]
ret = pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpuset);
^
bench/epoll-wait.c:345:4: error: nested extern declaration of 'pthread_attr_setaffinity_np' [-Werror=nested-externs]
ret = pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpuset);
^
bench/epoll-wait.c: In function 'bench_epoll_wait':
bench/epoll-wait.c:462:12: error: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'rlim_t {aka long long unsigned int}' [-Werror=format=]
printinfo("Setting RLIMIT_NOFILE rlimit from %lu to: %lu\n", prevrl.rlim_max, rl.rlim_max);
^
bench/epoll-wait.c:85:31: note: in definition of macro 'printinfo'
do { if (__verbose) { printf(fmt, ## arg); fflush(stdout); } } while (0)
^
bench/epoll-wait.c:462:12: error: format '%lu' expects argument of type 'long unsigned int', but argument 3 has type 'rlim_t {aka long long unsigned int}' [-Werror=format=]
printinfo("Setting RLIMIT_NOFILE rlimit from %lu to: %lu\n", prevrl.rlim_max, rl.rlim_max);
^
bench/epoll-wait.c:85:31: note: in definition of macro 'printinfo'
do { if (__verbose) { printf(fmt, ## arg); fflush(stdout); } } while (0)
^
CC /tmp/build/perf/util/trace-event-info.o
CC /tmp/build/perf/perf.o
cc1: all warnings being treated as errors
mv: can't rename '/tmp/build/perf/bench/.epoll-wait.o.tmp': No such file or directory
/git/linux/tools/build/Makefile.build:96: recipe for target '/tmp/build/perf/bench/epoll-wait.o' failed
make[4]: *** [/tmp/build/perf/bench/epoll-wait.o] Error 1
/git/linux/tools/build/Makefile.build:139: recipe for target 'bench' failed
make[3]: *** [bench] Error 2
make[3]: *** Waiting for unfinished jobs....
CC /tmp/build/perf/util/trace-event-scripting.o


On AmazonLinux 1 and 2, oops, these are mine:

LINK /tmp/build/perf/perf
/tmp/build/perf/perf-in.o: In function `bpf_map__set_filter_pids':
/git/linux/tools/perf/builtin-trace.c:2573: undefined reference to `bpf_map(float, double)'
/tmp/build/perf/perf-in.o: In function `cmd_trace':
(.text+0x4c71e): undefined reference to `bpf_map_update_elem'
/tmp/build/perf/perf-in.o: In function `cmd_trace':
(.text+0x4cab6): undefined reference to `bpf_map(float, double)'
/tmp/build/perf/perf-in.o: In function `cmd_trace':
(.text+0x4cae8): undefined reference to `bpf_map_update_elem'
collect2: error: ld returned 1 exit status
make[2]: *** [/tmp/build/perf/perf] Error 1
make[1]: *** [sub-make] Error 2
make: *** [all] Error 2
make: Leaving directory `/git/linux/tools/perf'
[root@seventh ~]#

I need to do that conditionally, i.e. it seems libbpf is not being built
in that distro...

centos:5 is about perf bench:

CC /tmp/build/perf/bench/epoll-wait.o
bench/epoll-wait.c:74:25: error: sys/eventfd.h: No such file or directory
cc1: warnings being treated as errors
bench/epoll-wait.c: In function 'do_threads':
bench/epoll-wait.c:328: warning: implicit declaration of function 'eventfd'
bench/epoll-wait.c:328: warning: nested extern declaration of 'eventfd'
bench/epoll-wait.c:328: error: 'EFD_NONBLOCK' undeclared (first use in this function)
bench/epoll-wait.c:328: error: (Each undeclared identifier is reported only once
bench/epoll-wait.c:328: error: for each function it appears in.)
mv: cannot stat `/tmp/build/perf/bench/.epoll-wait.o.tmp': No such file or directory
make[4]: *** [/tmp/build/perf/bench/epoll-wait.o] Error 1
make[3]: *** [bench] Error 2
make[3]: *** Waiting for unfinished jobs....
MKDIR /tmp/build/perf/tests/


centos:6 and 7 are ok with perf bench, not so much with my bpf changes :-\

LINK /tmp/build/perf/libperf-gtk.so
/tmp/build/perf/perf-in.o: In function `bpf_map__set_filter_pids':
/git/linux/tools/perf/builtin-trace.c:2573: undefined reference to `bpf_map(float, double)'
/git/linux/tools/perf/builtin-trace.c:2577: undefined reference to `bpf_map_update_elem'
/git/linux/tools/perf/builtin-trace.c:2573: undefined reference to `bpf_map(float, double)'
/git/linux/tools/perf/builtin-trace.c:2577: undefined reference to `bpf_map_update_elem'
collect2: ld returned 1 exit status
make[2]: *** [/tmp/build/perf/perf] Error 1
make[1]: *** [sub-make] Error 2
make: *** [all] Error 2
make: Leaving directory `/git/linux/tools/perf'
[root@seventh ~]#
[root@seventh ~]#

- Arnaldo

> - Arnaldo
>
> > diff --git a/tools/perf/bench/epoll-wait.c b/tools/perf/bench/epoll-wait.c
> > index c4c5ef60feb4..4e4efc5cfe22 100644
> > --- a/tools/perf/bench/epoll-wait.c
> > +++ b/tools/perf/bench/epoll-wait.c
> > @@ -215,13 +215,13 @@ static void *workerfn(void *arg)
> >
> > if (et) {
> > ev.events = EPOLLIN | EPOLLET;
> > - ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
> > + ret = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev);
> > }
> >
> > if (oneshot) {
> > /* rearm the file descriptor with a new event mask */
> > ev.events |= EPOLLIN | EPOLLONESHOT;
> > - ret = epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
> > + ret = epoll_ctl(efd, EPOLL_CTL_MOD, fd, &ev);
> > }
> >
> > ops++;
> > @@ -249,7 +249,7 @@ static void nest_epollfd(struct worker *w)
> >
> > for (i = 0; i < nested; i++) {
> > epollfdp[i] = epoll_create(1);
> > - if (epollfd < 0)
> > + if (epollfdp[i] < 0)
> > err(EXIT_FAILURE, "epoll_create");
> > }
> >

2018-11-09 21:08:46

by Davidlohr Bueso

[permalink] [raw]
Subject: Re: [PATCH 1/2] perf-bench: Add epoll parallel epoll_wait benchmark

On Thu, 08 Nov 2018, Arnaldo Carvalho de Melo wrote:

>Em Tue, Nov 06, 2018 at 04:13:25PM -0300, Arnaldo Carvalho de Melo escreveu:
> CC /tmp/build/perf/bench/futex-lock-pi.o
> CC /tmp/build/perf/bench/epoll-wait.o
>bench/epoll-wait.c: In function 'do_threads':
>bench/epoll-wait.c:345:10: error: implicit declaration of function 'pthread_attr_setaffinity_np' [-Werror=implicit-function-declaration]
> ret = pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpuset);

Ah, yes, how about the following?

-----8<-----------------------------------------------------
perf-bench: Move HAVE_PTHREAD_ATTR_SETAFFINITY_NP into bench.h

Both futex and epoll need this call, and can cause build failure
on systems that don't have it pthread_attr_setaffinity_np().

Signed-off-by: Davidlohr Bueso <[email protected]>
---
tools/perf/bench/bench.h | 11 +++++++++++
tools/perf/bench/futex.h | 12 ------------
2 files changed, 11 insertions(+), 12 deletions(-)

diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h
index 0e415464931b..fddb3ced9db6 100644
--- a/tools/perf/bench/bench.h
+++ b/tools/perf/bench/bench.h
@@ -51,4 +51,15 @@ int bench_epoll_ctl(int argc, const char **argv);
extern int bench_format;
extern unsigned int bench_repeat;

+#ifndef HAVE_PTHREAD_ATTR_SETAFFINITY_NP
+#include <pthread.h>
+#include <linux/compiler.h>
+static inline int pthread_attr_setaffinity_np(pthread_attr_t *attr __maybe_unused,
+ size_t cpusetsize __maybe_unused,
+ cpu_set_t *cpuset __maybe_unused)
+{
+ return 0;
+}
+#endif
+
#endif
diff --git a/tools/perf/bench/futex.h b/tools/perf/bench/futex.h
index db4853f209c7..31b53cc7d5bc 100644
--- a/tools/perf/bench/futex.h
+++ b/tools/perf/bench/futex.h
@@ -86,16 +86,4 @@ futex_cmp_requeue(u_int32_t *uaddr, u_int32_t val, u_int32_t *uaddr2, int nr_wak
return futex(uaddr, FUTEX_CMP_REQUEUE, nr_wake, nr_requeue, uaddr2,
val, opflags);
}
-
-#ifndef HAVE_PTHREAD_ATTR_SETAFFINITY_NP
-#include <pthread.h>
-#include <linux/compiler.h>
-static inline int pthread_attr_setaffinity_np(pthread_attr_t *attr __maybe_unused,
- size_t cpusetsize __maybe_unused,
- cpu_set_t *cpuset __maybe_unused)
-{
- return 0;
-}
-#endif
-
#endif /* _FUTEX_H */
--
2.16.4

2018-11-11 17:34:34

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH 1/2] perf-bench: Add epoll parallel epoll_wait benchmark

Em Fri, Nov 09, 2018 at 01:07:19PM -0800, Davidlohr Bueso escreveu:
> On Thu, 08 Nov 2018, Arnaldo Carvalho de Melo wrote:
>
> > Em Tue, Nov 06, 2018 at 04:13:25PM -0300, Arnaldo Carvalho de Melo escreveu:
> > CC /tmp/build/perf/bench/futex-lock-pi.o
> > CC /tmp/build/perf/bench/epoll-wait.o
> > bench/epoll-wait.c: In function 'do_threads':
> > bench/epoll-wait.c:345:10: error: implicit declaration of function 'pthread_attr_setaffinity_np' [-Werror=implicit-function-declaration]
> > ret = pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpuset);
>
> Ah, yes, how about the following?

Gets it a bit further, then we get this, which I think should be fixed
using some PRIu64, etc. I'll try to do that at some point, but in short
vacations now, then plumbers :-)

CC /tmp/build/perf/bench/epoll-wait.o
bench/epoll-wait.c: In function 'bench_epoll_wait':
bench/epoll-wait.c:462:12: error: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'rlim_t {aka long long unsigned int}' [-Werror=format=]
printinfo("Setting RLIMIT_NOFILE rlimit from %lu to: %lu\n", prevrl.rlim_max, rl.rlim_max);
^
bench/epoll-wait.c:85:31: note: in definition of macro 'printinfo'
do { if (__verbose) { printf(fmt, ## arg); fflush(stdout); } } while (0)
^
bench/epoll-wait.c:462:12: error: format '%lu' expects argument of type 'long unsigned int', but argument 3 has type 'rlim_t {aka long long unsigned int}' [-Werror=format=]
printinfo("Setting RLIMIT_NOFILE rlimit from %lu to: %lu\n", prevrl.rlim_max, rl.rlim_max);
^
bench/epoll-wait.c:85:31: note: in definition of macro 'printinfo'
do { if (__verbose) { printf(fmt, ## arg); fflush(stdout); } } while (0)
^
CC /tmp/build/perf/bench/epoll-ctl.o
bench/epoll-ctl.c: In function 'bench_epoll_ctl':
bench/epoll-ctl.c:343:12: error: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'rlim_t {aka long long unsigned int}' [-Werror=format=]
printinfo("Setting RLIMIT_NOFILE rlimit from %lu to: %lu\n",
^
bench/epoll-ctl.c:31:29: note: in definition of macro 'printinfo'
do { if (__verbose) printf(fmt, ## arg); } while (0)
^
bench/epoll-ctl.c:343:12: error: format '%lu' expects argument of type 'long unsigned int', but argument 3 has type 'rlim_t {aka long long unsigned int}' [-Werror=format=]
printinfo("Setting RLIMIT_NOFILE rlimit from %lu to: %lu\n",
^
bench/epoll-ctl.c:31:29: note: in definition of macro 'printinfo'
do { if (__verbose) printf(fmt, ## arg); } while (0)
^
cc1: all warnings being treated as errors
mv: can't rename '/tmp/build/perf/bench/.epoll-wait.o.tmp': No such file or directory
/git/linux/tools/build/Makefile.build:96: recipe for target '/tmp/build/perf/bench/epoll-wait.o' failed
make[4]: *** [/tmp/build/perf/bench/epoll-wait.o] Error 1
make[4]: *** Waiting for unfinished jobs....
CC /tmp/build/perf/perf.o
cc1: all warnings being treated as errors
mv: can't rename '/tmp/build/perf/bench/.epoll-ctl.o.tmp': No such file or directory
/git/linux/tools/build/Makefile.build:96: recipe for target '/tmp/build/perf/bench/epoll-ctl.o' failed
make[4]: *** [/tmp/build/perf/bench/epoll-ctl.o] Error 1
/git/linux/tools/build/Makefile.build:139: recipe for target 'bench' failed
make[3]: *** [bench] Error 2
make[3]: *** Waiting for unfinished jobs....
CC /tmp/build/perf/tests/attr.o
CC /tmp/build/perf/tests/vmlinux-kallsyms.o
CC /tmp/build/perf/tests/openat-syscall.o
CC /tmp/build/perf/util/map.o


> -----8<-----------------------------------------------------
> perf-bench: Move HAVE_PTHREAD_ATTR_SETAFFINITY_NP into bench.h
>
> Both futex and epoll need this call, and can cause build failure
> on systems that don't have it pthread_attr_setaffinity_np().
>
> Signed-off-by: Davidlohr Bueso <[email protected]>
> ---
> tools/perf/bench/bench.h | 11 +++++++++++
> tools/perf/bench/futex.h | 12 ------------
> 2 files changed, 11 insertions(+), 12 deletions(-)
>
> diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h
> index 0e415464931b..fddb3ced9db6 100644
> --- a/tools/perf/bench/bench.h
> +++ b/tools/perf/bench/bench.h
> @@ -51,4 +51,15 @@ int bench_epoll_ctl(int argc, const char **argv);
> extern int bench_format;
> extern unsigned int bench_repeat;
>
> +#ifndef HAVE_PTHREAD_ATTR_SETAFFINITY_NP
> +#include <pthread.h>
> +#include <linux/compiler.h>
> +static inline int pthread_attr_setaffinity_np(pthread_attr_t *attr __maybe_unused,
> + size_t cpusetsize __maybe_unused,
> + cpu_set_t *cpuset __maybe_unused)
> +{
> + return 0;
> +}
> +#endif
> +
> #endif
> diff --git a/tools/perf/bench/futex.h b/tools/perf/bench/futex.h
> index db4853f209c7..31b53cc7d5bc 100644
> --- a/tools/perf/bench/futex.h
> +++ b/tools/perf/bench/futex.h
> @@ -86,16 +86,4 @@ futex_cmp_requeue(u_int32_t *uaddr, u_int32_t val, u_int32_t *uaddr2, int nr_wak
> return futex(uaddr, FUTEX_CMP_REQUEUE, nr_wake, nr_requeue, uaddr2,
> val, opflags);
> }
> -
> -#ifndef HAVE_PTHREAD_ATTR_SETAFFINITY_NP
> -#include <pthread.h>
> -#include <linux/compiler.h>
> -static inline int pthread_attr_setaffinity_np(pthread_attr_t *attr __maybe_unused,
> - size_t cpusetsize __maybe_unused,
> - cpu_set_t *cpuset __maybe_unused)
> -{
> - return 0;
> -}
> -#endif
> -
> #endif /* _FUTEX_H */
> --
> 2.16.4

2018-11-12 05:40:31

by Davidlohr Bueso

[permalink] [raw]
Subject: Re: [PATCH 1/2] perf-bench: Add epoll parallel epoll_wait benchmark

On Sun, 11 Nov 2018, Arnaldo Carvalho de Melo wrote:
>Gets it a bit further, then we get this, which I think should be fixed
>using some PRIu64, etc. I'll try to do that at some point, but in short
>vacations now, then plumbers :-)

Yeah sorry, I didn't mean to suggest that was it. I can send the follow
up patches, or a v2 altogether if you prefer.

See you at lpc.

Thanks,
Davidlohr

Subject: [tip:perf/core] perf bench: Move HAVE_PTHREAD_ATTR_SETAFFINITY_NP into bench.h

Commit-ID: d47d77c3f008d3cf02c6ce92ef4f6e32ca270351
Gitweb: https://git.kernel.org/tip/d47d77c3f008d3cf02c6ce92ef4f6e32ca270351
Author: Davidlohr Bueso <[email protected]>
AuthorDate: Fri, 9 Nov 2018 13:07:19 -0800
Committer: Arnaldo Carvalho de Melo <[email protected]>
CommitDate: Wed, 21 Nov 2018 12:00:32 -0300

perf bench: Move HAVE_PTHREAD_ATTR_SETAFFINITY_NP into bench.h

Both futex and epoll need this call, and can cause build failure on
systems that don't have it pthread_attr_setaffinity_np().

Signed-off-by: Davidlohr Bueso <[email protected]>
Reported-by: Arnaldo Carvalho de Melo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Davidlohr Bueso <[email protected]>
Cc: Jason Baron <[email protected]>
Link: http://lkml.kernel.org/r/20181109210719.pr7ohayuwqmfp2wl@linux-r8p5
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
tools/perf/bench/bench.h | 11 +++++++++++
tools/perf/bench/futex.h | 12 ------------
2 files changed, 11 insertions(+), 12 deletions(-)

diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h
index 6c9fcd757f31..8299c76046cd 100644
--- a/tools/perf/bench/bench.h
+++ b/tools/perf/bench/bench.h
@@ -48,4 +48,15 @@ int bench_futex_lock_pi(int argc, const char **argv);
extern int bench_format;
extern unsigned int bench_repeat;

+#ifndef HAVE_PTHREAD_ATTR_SETAFFINITY_NP
+#include <pthread.h>
+#include <linux/compiler.h>
+static inline int pthread_attr_setaffinity_np(pthread_attr_t *attr __maybe_unused,
+ size_t cpusetsize __maybe_unused,
+ cpu_set_t *cpuset __maybe_unused)
+{
+ return 0;
+}
+#endif
+
#endif
diff --git a/tools/perf/bench/futex.h b/tools/perf/bench/futex.h
index db4853f209c7..31b53cc7d5bc 100644
--- a/tools/perf/bench/futex.h
+++ b/tools/perf/bench/futex.h
@@ -86,16 +86,4 @@ futex_cmp_requeue(u_int32_t *uaddr, u_int32_t val, u_int32_t *uaddr2, int nr_wak
return futex(uaddr, FUTEX_CMP_REQUEUE, nr_wake, nr_requeue, uaddr2,
val, opflags);
}
-
-#ifndef HAVE_PTHREAD_ATTR_SETAFFINITY_NP
-#include <pthread.h>
-#include <linux/compiler.h>
-static inline int pthread_attr_setaffinity_np(pthread_attr_t *attr __maybe_unused,
- size_t cpusetsize __maybe_unused,
- cpu_set_t *cpuset __maybe_unused)
-{
- return 0;
-}
-#endif
-
#endif /* _FUTEX_H */

Subject: [tip:perf/core] perf bench: Add epoll parallel epoll_wait benchmark

Commit-ID: 121dd9ea0116de3e79a4903a84018190c595e2b6
Gitweb: https://git.kernel.org/tip/121dd9ea0116de3e79a4903a84018190c595e2b6
Author: Davidlohr Bueso <[email protected]>
AuthorDate: Tue, 6 Nov 2018 07:22:25 -0800
Committer: Arnaldo Carvalho de Melo <[email protected]>
CommitDate: Wed, 21 Nov 2018 22:38:47 -0300

perf bench: Add epoll parallel epoll_wait benchmark

This program benchmarks concurrent epoll_wait(2) for file descriptors
that are monitored with with EPOLLIN along various semantics, by a
single epoll instance. Such conditions can be found when using
single/combined or multiple queuing when load balancing.

Each thread has a number of private, nonblocking file descriptors,
referred to as fdmap. A writer thread will constantly be writing to the
fdmaps of all threads, minimizing each threads's chances of epoll_wait
not finding any ready read events and blocking as this is not what we
want to stress. Full details in the start of the C file.

Committer testing:

# perf bench
Usage:
perf bench [<common options>] <collection> <benchmark> [<options>]

# List of all available benchmark collections:

sched: Scheduler and IPC benchmarks
mem: Memory access benchmarks
numa: NUMA scheduling and MM benchmarks
futex: Futex stressing benchmarks
epoll: Epoll stressing benchmarks
all: All benchmarks

# perf bench epoll

# List of available benchmarks for collection 'epoll':

wait: Benchmark epoll concurrent epoll_waits
all: Run all futex benchmarks

# perf bench epoll wait
# Running 'epoll/wait' benchmark:
Run summary [PID 19295]: 3 threads monitoring on 64 file-descriptors for 8 secs.

[thread 0] fdmap: 0xdaa650 ... 0xdaa74c [ 328241 ops/sec ]
[thread 1] fdmap: 0xdaa900 ... 0xdaa9fc [ 351695 ops/sec ]
[thread 2] fdmap: 0xdaabb0 ... 0xdaacac [ 381423 ops/sec ]

Averaged 353786 operations/sec (+- 4.35%), total secs = 8
#

Committer notes:

Fix the build on debian:experimental-x-mips, debian:experimental-x-mipsel
and others:

CC /tmp/build/perf/bench/epoll-wait.o
bench/epoll-wait.c: In function 'writerfn':
bench/epoll-wait.c:399:12: error: format '%ld' expects argument of type 'long int', but argument 2 has type 'size_t' {aka 'unsigned int'} [-Werror=format=]
printinfo("exiting writer-thread (total full-loops: %ld)\n", iter);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~
bench/epoll-wait.c:86:31: note: in definition of macro 'printinfo'
do { if (__verbose) { printf(fmt, ## arg); fflush(stdout); } } while (0)
^~~
cc1: all warnings being treated as errors

Signed-off-by: Davidlohr Bueso <[email protected]>
Tested-by: Arnaldo Carvalho de Melo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Davidlohr Bueso <[email protected]>
Cc: Jason Baron <[email protected]> <[email protected]>
Link: http://lkml.kernel.org/r/[email protected]
Link: http://lkml.kernel.org/r/20181106182349.thdkpvshkna5vd7o@linux-r8p5>
[ Applied above fixup as per Davidlohr's request ]
[ Use inttypes.h to print rlim_t fields, fixing the build on Alpine Linux / musl libc ]
[ Check if eventfd() is available, i.e. if HAVE_EVENTFD is defined ]
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
tools/perf/Documentation/perf-bench.txt | 7 +
tools/perf/bench/Build | 2 +
tools/perf/bench/bench.h | 2 +
tools/perf/bench/epoll-wait.c | 540 ++++++++++++++++++++++++++++++++
tools/perf/builtin-bench.c | 12 +
5 files changed, 563 insertions(+)

diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt
index 34750fc32714..3a6b2e73b2e8 100644
--- a/tools/perf/Documentation/perf-bench.txt
+++ b/tools/perf/Documentation/perf-bench.txt
@@ -58,6 +58,9 @@ SUBSYSTEM
'futex'::
Futex stressing benchmarks.

+'epoll'::
+ Eventpoll (epoll) stressing benchmarks.
+
'all'::
All benchmark subsystems.

@@ -203,6 +206,10 @@ Suite for evaluating requeue calls.
*lock-pi*::
Suite for evaluating futex lock_pi calls.

+SUITES FOR 'epoll'
+~~~~~~~~~~~~~~~~~~
+*wait*::
+Suite for evaluating concurrent epoll_wait calls.

SEE ALSO
--------
diff --git a/tools/perf/bench/Build b/tools/perf/bench/Build
index eafce1a130a1..2bb79b542d53 100644
--- a/tools/perf/bench/Build
+++ b/tools/perf/bench/Build
@@ -7,6 +7,8 @@ perf-y += futex-wake-parallel.o
perf-y += futex-requeue.o
perf-y += futex-lock-pi.o

+perf-y += epoll-wait.o
+
perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-lib.o
perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-asm.o
perf-$(CONFIG_X86_64) += mem-memset-x86-64-asm.o
diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h
index 8299c76046cd..6e1f091ced96 100644
--- a/tools/perf/bench/bench.h
+++ b/tools/perf/bench/bench.h
@@ -38,6 +38,8 @@ int bench_futex_requeue(int argc, const char **argv);
/* pi futexes */
int bench_futex_lock_pi(int argc, const char **argv);

+int bench_epoll_wait(int argc, const char **argv);
+
#define BENCH_FORMAT_DEFAULT_STR "default"
#define BENCH_FORMAT_DEFAULT 0
#define BENCH_FORMAT_SIMPLE_STR "simple"
diff --git a/tools/perf/bench/epoll-wait.c b/tools/perf/bench/epoll-wait.c
new file mode 100644
index 000000000000..5a11534e96a0
--- /dev/null
+++ b/tools/perf/bench/epoll-wait.c
@@ -0,0 +1,540 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifdef HAVE_EVENTFD
+/*
+ * Copyright (C) 2018 Davidlohr Bueso.
+ *
+ * This program benchmarks concurrent epoll_wait(2) monitoring multiple
+ * file descriptors under one or two load balancing models. The first,
+ * and default, is the single/combined queueing (which refers to a single
+ * epoll instance for N worker threads):
+ *
+ * |---> [worker A]
+ * |---> [worker B]
+ * [combined queue] .---> [worker C]
+ * |---> [worker D]
+ * |---> [worker E]
+ *
+ * While the second model, enabled via --multiq option, uses multiple
+ * queueing (which refers to one epoll instance per worker). For example,
+ * short lived tcp connections in a high throughput httpd server will
+ * ditribute the accept()'ing connections across CPUs. In this case each
+ * worker does a limited amount of processing.
+ *
+ * [queue A] ---> [worker]
+ * [queue B] ---> [worker]
+ * [queue C] ---> [worker]
+ * [queue D] ---> [worker]
+ * [queue E] ---> [worker]
+ *
+ * Naturally, the single queue will enforce more concurrency on the epoll
+ * instance, and can therefore scale poorly compared to multiple queues.
+ * However, this is a benchmark raw data and must be taken with a grain of
+ * salt when choosing how to make use of sys_epoll.
+
+ * Each thread has a number of private, nonblocking file descriptors,
+ * referred to as fdmap. A writer thread will constantly be writing to
+ * the fdmaps of all threads, minimizing each threads's chances of
+ * epoll_wait not finding any ready read events and blocking as this
+ * is not what we want to stress. The size of the fdmap can be adjusted
+ * by the user; enlarging the value will increase the chances of
+ * epoll_wait(2) blocking as the lineal writer thread will take "longer",
+ * at least at a high level.
+ *
+ * Note that because fds are private to each thread, this workload does
+ * not stress scenarios where multiple tasks are awoken per ready IO; ie:
+ * EPOLLEXCLUSIVE semantics.
+ *
+ * The end result/metric is throughput: number of ops/second where an
+ * operation consists of:
+ *
+ * epoll_wait(2) + [others]
+ *
+ * ... where [others] is the cost of re-adding the fd (EPOLLET),
+ * or rearming it (EPOLLONESHOT).
+ *
+ *
+ * The purpose of this is program is that it be useful for measuring
+ * kernel related changes to the sys_epoll, and not comparing different
+ * IO polling methods, for example. Hence everything is very adhoc and
+ * outputs raw microbenchmark numbers. Also this uses eventfd, similar
+ * tools tend to use pipes or sockets, but the result is the same.
+ */
+
+/* For the CLR_() macros */
+#include <string.h>
+#include <pthread.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <sys/types.h>
+
+#include "../util/stat.h"
+#include <subcmd/parse-options.h>
+#include "bench.h"
+#include "cpumap.h"
+
+#include <err.h>
+
+#define printinfo(fmt, arg...) \
+ do { if (__verbose) { printf(fmt, ## arg); fflush(stdout); } } while (0)
+
+static unsigned int nthreads = 0;
+static unsigned int nsecs = 8;
+struct timeval start, end, runtime;
+static bool wdone, done, __verbose, randomize, nonblocking;
+
+/*
+ * epoll related shared variables.
+ */
+
+/* Maximum number of nesting allowed inside epoll sets */
+#define EPOLL_MAXNESTS 4
+
+static int epollfd;
+static int *epollfdp;
+static bool noaffinity;
+static unsigned int nested = 0;
+static bool et; /* edge-trigger */
+static bool oneshot;
+static bool multiq; /* use an epoll instance per thread */
+
+/* amount of fds to monitor, per thread */
+static unsigned int nfds = 64;
+
+static pthread_mutex_t thread_lock;
+static unsigned int threads_starting;
+static struct stats throughput_stats;
+static pthread_cond_t thread_parent, thread_worker;
+
+struct worker {
+ int tid;
+ int epollfd; /* for --multiq */
+ pthread_t thread;
+ unsigned long ops;
+ int *fdmap;
+};
+
+static const struct option options[] = {
+ /* general benchmark options */
+ OPT_UINTEGER('t', "threads", &nthreads, "Specify amount of threads"),
+ OPT_UINTEGER('r', "runtime", &nsecs, "Specify runtime (in seconds)"),
+ OPT_UINTEGER('f', "nfds", &nfds, "Specify amount of file descriptors to monitor for each thread"),
+ OPT_BOOLEAN( 'n', "noaffinity", &noaffinity, "Disables CPU affinity"),
+ OPT_BOOLEAN('R', "randomize", &randomize, "Enable random write behaviour (default is lineal)"),
+ OPT_BOOLEAN( 'v', "verbose", &__verbose, "Verbose mode"),
+
+ /* epoll specific options */
+ OPT_BOOLEAN( 'm', "multiq", &multiq, "Use multiple epoll instances (one per thread)"),
+ OPT_BOOLEAN( 'B', "nonblocking", &nonblocking, "Nonblocking epoll_wait(2) behaviour"),
+ OPT_UINTEGER( 'N', "nested", &nested, "Nesting level epoll hierarchy (default is 0, no nesting)"),
+ OPT_BOOLEAN( 'S', "oneshot", &oneshot, "Use EPOLLONESHOT semantics"),
+ OPT_BOOLEAN( 'E', "edge", &et, "Use Edge-triggered interface (default is LT)"),
+
+ OPT_END()
+};
+
+static const char * const bench_epoll_wait_usage[] = {
+ "perf bench epoll wait <options>",
+ NULL
+};
+
+
+/*
+ * Arrange the N elements of ARRAY in random order.
+ * Only effective if N is much smaller than RAND_MAX;
+ * if this may not be the case, use a better random
+ * number generator. -- Ben Pfaff.
+ */
+static void shuffle(void *array, size_t n, size_t size)
+{
+ char *carray = array;
+ void *aux;
+ size_t i;
+
+ if (n <= 1)
+ return;
+
+ aux = calloc(1, size);
+ if (!aux)
+ err(EXIT_FAILURE, "calloc");
+
+ for (i = 1; i < n; ++i) {
+ size_t j = i + rand() / (RAND_MAX / (n - i) + 1);
+ j *= size;
+
+ memcpy(aux, &carray[j], size);
+ memcpy(&carray[j], &carray[i*size], size);
+ memcpy(&carray[i*size], aux, size);
+ }
+
+ free(aux);
+}
+
+
+static void *workerfn(void *arg)
+{
+ int fd, ret, r;
+ struct worker *w = (struct worker *) arg;
+ unsigned long ops = w->ops;
+ struct epoll_event ev;
+ uint64_t val;
+ int to = nonblocking? 0 : -1;
+ int efd = multiq ? w->epollfd : epollfd;
+
+ pthread_mutex_lock(&thread_lock);
+ threads_starting--;
+ if (!threads_starting)
+ pthread_cond_signal(&thread_parent);
+ pthread_cond_wait(&thread_worker, &thread_lock);
+ pthread_mutex_unlock(&thread_lock);
+
+ do {
+ /*
+ * Block undefinitely waiting for the IN event.
+ * In order to stress the epoll_wait(2) syscall,
+ * call it event per event, instead of a larger
+ * batch (max)limit.
+ */
+ do {
+ ret = epoll_wait(efd, &ev, 1, to);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0)
+ err(EXIT_FAILURE, "epoll_wait");
+
+ fd = ev.data.fd;
+
+ do {
+ r = read(fd, &val, sizeof(val));
+ } while (!done && (r < 0 && errno == EAGAIN));
+
+ if (et) {
+ ev.events = EPOLLIN | EPOLLET;
+ ret = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev);
+ }
+
+ if (oneshot) {
+ /* rearm the file descriptor with a new event mask */
+ ev.events |= EPOLLIN | EPOLLONESHOT;
+ ret = epoll_ctl(efd, EPOLL_CTL_MOD, fd, &ev);
+ }
+
+ ops++;
+ } while (!done);
+
+ if (multiq)
+ close(w->epollfd);
+
+ w->ops = ops;
+ return NULL;
+}
+
+static void nest_epollfd(struct worker *w)
+{
+ unsigned int i;
+ struct epoll_event ev;
+ int efd = multiq ? w->epollfd : epollfd;
+
+ if (nested > EPOLL_MAXNESTS)
+ nested = EPOLL_MAXNESTS;
+
+ epollfdp = calloc(nested, sizeof(*epollfdp));
+ if (!epollfdp)
+ err(EXIT_FAILURE, "calloc");
+
+ for (i = 0; i < nested; i++) {
+ epollfdp[i] = epoll_create(1);
+ if (epollfdp[i] < 0)
+ err(EXIT_FAILURE, "epoll_create");
+ }
+
+ ev.events = EPOLLHUP; /* anything */
+ ev.data.u64 = i; /* any number */
+
+ for (i = nested - 1; i; i--) {
+ if (epoll_ctl(epollfdp[i - 1], EPOLL_CTL_ADD,
+ epollfdp[i], &ev) < 0)
+ err(EXIT_FAILURE, "epoll_ctl");
+ }
+
+ if (epoll_ctl(efd, EPOLL_CTL_ADD, *epollfdp, &ev) < 0)
+ err(EXIT_FAILURE, "epoll_ctl");
+}
+
+static void toggle_done(int sig __maybe_unused,
+ siginfo_t *info __maybe_unused,
+ void *uc __maybe_unused)
+{
+ /* inform all threads that we're done for the day */
+ done = true;
+ gettimeofday(&end, NULL);
+ timersub(&end, &start, &runtime);
+}
+
+static void print_summary(void)
+{
+ unsigned long avg = avg_stats(&throughput_stats);
+ double stddev = stddev_stats(&throughput_stats);
+
+ printf("\nAveraged %ld operations/sec (+- %.2f%%), total secs = %d\n",
+ avg, rel_stddev_stats(stddev, avg),
+ (int) runtime.tv_sec);
+}
+
+static int do_threads(struct worker *worker, struct cpu_map *cpu)
+{
+ pthread_attr_t thread_attr, *attrp = NULL;
+ cpu_set_t cpuset;
+ unsigned int i, j;
+ int ret, events = EPOLLIN;
+
+ if (oneshot)
+ events |= EPOLLONESHOT;
+ if (et)
+ events |= EPOLLET;
+
+ printinfo("starting worker/consumer %sthreads%s\n",
+ noaffinity ? "":"CPU affinity ",
+ nonblocking ? " (nonblocking)":"");
+ if (!noaffinity)
+ pthread_attr_init(&thread_attr);
+
+ for (i = 0; i < nthreads; i++) {
+ struct worker *w = &worker[i];
+
+ if (multiq) {
+ w->epollfd = epoll_create(1);
+ if (w->epollfd < 0)
+ err(EXIT_FAILURE, "epoll_create");
+
+ if (nested)
+ nest_epollfd(w);
+ }
+
+ w->tid = i;
+ w->fdmap = calloc(nfds, sizeof(int));
+ if (!w->fdmap)
+ return 1;
+
+ for (j = 0; j < nfds; j++) {
+ int efd = multiq ? w->epollfd : epollfd;
+ struct epoll_event ev;
+
+ w->fdmap[j] = eventfd(0, EFD_NONBLOCK);
+ if (w->fdmap[j] < 0)
+ err(EXIT_FAILURE, "eventfd");
+
+ ev.data.fd = w->fdmap[j];
+ ev.events = events;
+
+ ret = epoll_ctl(efd, EPOLL_CTL_ADD,
+ w->fdmap[j], &ev);
+ if (ret < 0)
+ err(EXIT_FAILURE, "epoll_ctl");
+ }
+
+ if (!noaffinity) {
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpu->map[i % cpu->nr], &cpuset);
+
+ ret = pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpuset);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_attr_setaffinity_np");
+
+ attrp = &thread_attr;
+ }
+
+ ret = pthread_create(&w->thread, attrp, workerfn,
+ (void *)(struct worker *) w);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_create");
+ }
+
+ if (!noaffinity)
+ pthread_attr_destroy(&thread_attr);
+
+ return ret;
+}
+
+static void *writerfn(void *p)
+{
+ struct worker *worker = p;
+ size_t i, j, iter;
+ const uint64_t val = 1;
+ ssize_t sz;
+ struct timespec ts = { .tv_sec = 0,
+ .tv_nsec = 500 };
+
+ printinfo("starting writer-thread: doing %s writes ...\n",
+ randomize? "random":"lineal");
+
+ for (iter = 0; !wdone; iter++) {
+ if (randomize) {
+ shuffle((void *)worker, nthreads, sizeof(*worker));
+ }
+
+ for (i = 0; i < nthreads; i++) {
+ struct worker *w = &worker[i];
+
+ if (randomize) {
+ shuffle((void *)w->fdmap, nfds, sizeof(int));
+ }
+
+ for (j = 0; j < nfds; j++) {
+ do {
+ sz = write(w->fdmap[j], &val, sizeof(val));
+ } while (!wdone && (sz < 0 && errno == EAGAIN));
+ }
+ }
+
+ nanosleep(&ts, NULL);
+ }
+
+ printinfo("exiting writer-thread (total full-loops: %zd)\n", iter);
+ return NULL;
+}
+
+static int cmpworker(const void *p1, const void *p2)
+{
+
+ struct worker *w1 = (struct worker *) p1;
+ struct worker *w2 = (struct worker *) p2;
+ return w1->tid > w2->tid;
+}
+
+int bench_epoll_wait(int argc, const char **argv)
+{
+ int ret = 0;
+ struct sigaction act;
+ unsigned int i;
+ struct worker *worker = NULL;
+ struct cpu_map *cpu;
+ pthread_t wthread;
+ struct rlimit rl, prevrl;
+
+ argc = parse_options(argc, argv, options, bench_epoll_wait_usage, 0);
+ if (argc) {
+ usage_with_options(bench_epoll_wait_usage, options);
+ exit(EXIT_FAILURE);
+ }
+
+ sigfillset(&act.sa_mask);
+ act.sa_sigaction = toggle_done;
+ sigaction(SIGINT, &act, NULL);
+
+ cpu = cpu_map__new(NULL);
+ if (!cpu)
+ goto errmem;
+
+ /* a single, main epoll instance */
+ if (!multiq) {
+ epollfd = epoll_create(1);
+ if (epollfd < 0)
+ err(EXIT_FAILURE, "epoll_create");
+
+ /*
+ * Deal with nested epolls, if any.
+ */
+ if (nested)
+ nest_epollfd(NULL);
+ }
+
+ printinfo("Using %s queue model\n", multiq ? "multi" : "single");
+ printinfo("Nesting level(s): %d\n", nested);
+
+ /* default to the number of CPUs and leave one for the writer pthread */
+ if (!nthreads)
+ nthreads = cpu->nr - 1;
+
+ worker = calloc(nthreads, sizeof(*worker));
+ if (!worker) {
+ goto errmem;
+ }
+
+ if (getrlimit(RLIMIT_NOFILE, &prevrl))
+ err(EXIT_FAILURE, "getrlimit");
+ rl.rlim_cur = rl.rlim_max = nfds * nthreads * 2 + 50;
+ printinfo("Setting RLIMIT_NOFILE rlimit from %" PRIu64 " to: %" PRIu64 "\n",
+ (uint64_t)prevrl.rlim_max, (uint64_t)rl.rlim_max);
+ if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
+ err(EXIT_FAILURE, "setrlimit");
+
+ printf("Run summary [PID %d]: %d threads monitoring%s on "
+ "%d file-descriptors for %d secs.\n\n",
+ getpid(), nthreads, oneshot ? " (EPOLLONESHOT semantics)": "", nfds, nsecs);
+
+ init_stats(&throughput_stats);
+ pthread_mutex_init(&thread_lock, NULL);
+ pthread_cond_init(&thread_parent, NULL);
+ pthread_cond_init(&thread_worker, NULL);
+
+ threads_starting = nthreads;
+
+ gettimeofday(&start, NULL);
+
+ do_threads(worker, cpu);
+
+ pthread_mutex_lock(&thread_lock);
+ while (threads_starting)
+ pthread_cond_wait(&thread_parent, &thread_lock);
+ pthread_cond_broadcast(&thread_worker);
+ pthread_mutex_unlock(&thread_lock);
+
+ /*
+ * At this point the workers should be blocked waiting for read events
+ * to become ready. Launch the writer which will constantly be writing
+ * to each thread's fdmap.
+ */
+ ret = pthread_create(&wthread, NULL, writerfn,
+ (void *)(struct worker *) worker);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_create");
+
+ sleep(nsecs);
+ toggle_done(0, NULL, NULL);
+ printinfo("main thread: toggling done\n");
+
+ sleep(1); /* meh */
+ wdone = true;
+ ret = pthread_join(wthread, NULL);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_join");
+
+ /* cleanup & report results */
+ pthread_cond_destroy(&thread_parent);
+ pthread_cond_destroy(&thread_worker);
+ pthread_mutex_destroy(&thread_lock);
+
+ /* sort the array back before reporting */
+ if (randomize)
+ qsort(worker, nthreads, sizeof(struct worker), cmpworker);
+
+ for (i = 0; i < nthreads; i++) {
+ unsigned long t = worker[i].ops/runtime.tv_sec;
+
+ update_stats(&throughput_stats, t);
+
+ if (nfds == 1)
+ printf("[thread %2d] fdmap: %p [ %04ld ops/sec ]\n",
+ worker[i].tid, &worker[i].fdmap[0], t);
+ else
+ printf("[thread %2d] fdmap: %p ... %p [ %04ld ops/sec ]\n",
+ worker[i].tid, &worker[i].fdmap[0],
+ &worker[i].fdmap[nfds-1], t);
+ }
+
+ print_summary();
+
+ close(epollfd);
+ return ret;
+errmem:
+ err(EXIT_FAILURE, "calloc");
+}
+#endif // HAVE_EVENTFD
diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c
index 17a6bcd01aa6..55efd23c3efb 100644
--- a/tools/perf/builtin-bench.c
+++ b/tools/perf/builtin-bench.c
@@ -14,6 +14,7 @@
* mem ... memory access performance
* numa ... NUMA scheduling and MM performance
* futex ... Futex performance
+ * epoll ... Event poll performance
*/
#include "perf.h"
#include "util/util.h"
@@ -67,6 +68,14 @@ static struct bench futex_benchmarks[] = {
{ NULL, NULL, NULL }
};

+#ifdef HAVE_EVENTFD
+static struct bench epoll_benchmarks[] = {
+ { "wait", "Benchmark epoll concurrent epoll_waits", bench_epoll_wait },
+ { "all", "Run all futex benchmarks", NULL },
+ { NULL, NULL, NULL }
+};
+#endif // HAVE_EVENTFD
+
struct collection {
const char *name;
const char *summary;
@@ -80,6 +89,9 @@ static struct collection collections[] = {
{ "numa", "NUMA scheduling and MM benchmarks", numa_benchmarks },
#endif
{"futex", "Futex stressing benchmarks", futex_benchmarks },
+#ifdef HAVE_EVENTFD
+ {"epoll", "Epoll stressing benchmarks", epoll_benchmarks },
+#endif
{ "all", "All benchmarks", NULL },
{ NULL, NULL, NULL }
};

Subject: [tip:perf/core] perf bench: Add epoll_ctl(2) benchmark

Commit-ID: 231457ec707475c71d4e538a3253f1ed9e294cf0
Gitweb: https://git.kernel.org/tip/231457ec707475c71d4e538a3253f1ed9e294cf0
Author: Davidlohr Bueso <[email protected]>
AuthorDate: Tue, 6 Nov 2018 07:22:26 -0800
Committer: Arnaldo Carvalho de Melo <[email protected]>
CommitDate: Wed, 21 Nov 2018 22:39:55 -0300

perf bench: Add epoll_ctl(2) benchmark

Benchmark the various operations allowed for epoll_ctl(2). The idea is
to concurrently stress a single epoll instance doing add/mod/del
operations.

Committer testing:

# perf bench epoll ctl
# Running 'epoll/ctl' benchmark:
Run summary [PID 20344]: 4 threads doing epoll_ctl ops 64 file-descriptors for 8 secs.

[thread 0] fdmap: 0x21a46b0 ... 0x21a47ac [ add: 1680960 ops; mod: 1680960 ops; del: 1680960 ops ]
[thread 1] fdmap: 0x21a4960 ... 0x21a4a5c [ add: 1685440 ops; mod: 1685440 ops; del: 1685440 ops ]
[thread 2] fdmap: 0x21a4c10 ... 0x21a4d0c [ add: 1674368 ops; mod: 1674368 ops; del: 1674368 ops ]
[thread 3] fdmap: 0x21a4ec0 ... 0x21a4fbc [ add: 1677568 ops; mod: 1677568 ops; del: 1677568 ops ]

Averaged 1679584 ADD operations (+- 0.14%)
Averaged 1679584 MOD operations (+- 0.14%)
Averaged 1679584 DEL operations (+- 0.14%)
#

Lets measure those calls with 'perf trace' to get a glympse at what this
benchmark is doing in terms of syscalls:

# perf trace -m32768 -s perf bench epoll ctl
# Running 'epoll/ctl' benchmark:
Run summary [PID 20405]: 4 threads doing epoll_ctl ops 64 file-descriptors for 8 secs.

[thread 0] fdmap: 0x21764e0 ... 0x21765dc [ add: 1100480 ops; mod: 1100480 ops; del: 1100480 ops ]
[thread 1] fdmap: 0x2176790 ... 0x217688c [ add: 1250176 ops; mod: 1250176 ops; del: 1250176 ops ]
[thread 2] fdmap: 0x2176a40 ... 0x2176b3c [ add: 1022464 ops; mod: 1022464 ops; del: 1022464 ops ]
[thread 3] fdmap: 0x2176cf0 ... 0x2176dec [ add: 705472 ops; mod: 705472 ops; del: 705472 ops ]

Averaged 1019648 ADD operations (+- 11.27%)
Averaged 1019648 MOD operations (+- 11.27%)
Averaged 1019648 DEL operations (+- 11.27%)

Summary of events:

epoll-ctl (20405), 1264 events, 0.0%

syscall calls total min avg max stddev
(msec) (msec) (msec) (msec) (%)
--------------- -------- --------- --------- --------- --------- ------
eventfd2 256 9.514 0.001 0.037 5.243 68.00%
clone 4 1.245 0.204 0.311 0.531 24.13%
mprotect 66 0.345 0.002 0.005 0.021 7.43%
openat 45 0.313 0.004 0.007 0.073 21.93%
mmap 88 0.302 0.002 0.003 0.013 5.02%
futex 4 0.160 0.002 0.040 0.140 83.43%
sched_setaffinity 4 0.124 0.005 0.031 0.070 49.39%
read 44 0.103 0.001 0.002 0.013 15.54%
fstat 40 0.052 0.001 0.001 0.003 5.43%
close 39 0.039 0.001 0.001 0.001 1.48%
stat 9 0.034 0.003 0.004 0.006 7.30%
access 3 0.023 0.007 0.008 0.008 4.25%
open 2 0.021 0.008 0.011 0.013 22.60%
getdents 4 0.019 0.001 0.005 0.009 37.15%
write 2 0.013 0.004 0.007 0.009 38.48%
munmap 1 0.010 0.010 0.010 0.010 0.00%
brk 3 0.006 0.001 0.002 0.003 26.34%
rt_sigprocmask 2 0.004 0.001 0.002 0.003 43.95%
rt_sigaction 3 0.004 0.001 0.001 0.002 16.07%
prlimit64 3 0.004 0.001 0.001 0.001 5.39%
prctl 1 0.003 0.003 0.003 0.003 0.00%
epoll_create 1 0.003 0.003 0.003 0.003 0.00%
lseek 2 0.002 0.001 0.001 0.001 11.42%
sched_getaffinity 1 0.002 0.002 0.002 0.002 0.00%
arch_prctl 1 0.002 0.002 0.002 0.002 0.00%
set_tid_address 1 0.001 0.001 0.001 0.001 0.00%
getpid 1 0.001 0.001 0.001 0.001 0.00%
set_robust_list 1 0.001 0.001 0.001 0.001 0.00%
execve 1 0.000 0.000 0.000 0.000 0.00%

epoll-ctl (20406), 1245480 events, 14.6%

syscall calls total min avg max stddev
(msec) (msec) (msec) (msec) (%)
--------------- -------- --------- --------- --------- --------- ------
epoll_ctl 619511 1034.927 0.001 0.002 6.691 0.67%
nanosleep 3226 616.114 0.006 0.191 10.376 7.57%
futex 2 11.336 0.002 5.668 11.334 99.97%
set_robust_list 1 0.001 0.001 0.001 0.001 0.00%
clone 1 0.000 0.000 0.000 0.000 0.00%

epoll-ctl (20407), 1243151 events, 14.5%

syscall calls total min avg max stddev
(msec) (msec) (msec) (msec) (%)
--------------- -------- --------- --------- --------- --------- ------
epoll_ctl 618350 1042.181 0.001 0.002 2.512 0.40%
nanosleep 3220 366.261 0.012 0.114 18.162 9.59%
futex 4 5.463 0.001 1.366 5.427 99.12%
set_robust_list 1 0.002 0.002 0.002 0.002 0.00%

epoll-ctl (20408), 1801690 events, 21.1%

syscall calls total min avg max stddev
(msec) (msec) (msec) (msec) (%)
--------------- -------- --------- --------- --------- --------- ------
epoll_ctl 896174 1540.581 0.001 0.002 6.987 0.74%
nanosleep 4667 783.393 0.006 0.168 10.419 7.10%
futex 2 4.682 0.002 2.341 4.681 99.93%
set_robust_list 1 0.002 0.002 0.002 0.002 0.00%
clone 1 0.000 0.000 0.000 0.000 0.00%

epoll-ctl (20409), 4254890 events, 49.8%

syscall calls total min avg max stddev
(msec) (msec) (msec) (msec) (%)
--------------- -------- --------- --------- --------- --------- ------
epoll_ctl 2116416 3768.097 0.001 0.002 9.956 0.41%
nanosleep 11023 1141.778 0.006 0.104 9.447 4.95%
futex 3 0.037 0.002 0.012 0.029 70.50%
set_robust_list 1 0.008 0.008 0.008 0.008 0.00%
madvise 1 0.005 0.005 0.005 0.005 0.00%
clone 1 0.000 0.000 0.000 0.000 0.00%
#

Committer notes:

Fix build on fedora:24-x-ARC-uClibc, debian:experimental-x-mips,
debian:experimental-x-mipsel, ubuntu:16.04-x-arm and ubuntu:16.04-x-powerpc

CC /tmp/build/perf/bench/epoll-ctl.o
bench/epoll-ctl.c: In function 'init_fdmaps':
bench/epoll-ctl.c:214:16: error: comparison between signed and unsigned integer expressions [-Werror=sign-compare]
for (i = 0; i < nfds; i+=inc) {
^
bench/epoll-ctl.c: In function 'bench_epoll_ctl':
bench/epoll-ctl.c:377:16: error: comparison between signed and unsigned integer expressions [-Werror=sign-compare]
for (i = 0; i < nthreads; i++) {
^
bench/epoll-ctl.c:388:16: error: comparison between signed and unsigned integer expressions [-Werror=sign-compare]
for (i = 0; i < nthreads; i++) {
^
cc1: all warnings being treated as errors

Signed-off-by: Davidlohr Bueso <[email protected]>
Tested-by: Arnaldo Carvalho de Melo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Davidlohr Bueso <[email protected]>
Cc: Jason Baron <[email protected]>
Link: http://lkml.kernel.org/r/[email protected]
[ Use inttypes.h to print rlim_t fields, fixing the build on Alpine Linux / musl libc ]
[ Check if eventfd() is available, i.e. if HAVE_EVENTFD is defined ]
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
tools/perf/Documentation/perf-bench.txt | 3 +
tools/perf/bench/Build | 1 +
tools/perf/bench/bench.h | 1 +
tools/perf/bench/epoll-ctl.c | 413 ++++++++++++++++++++++++++++++++
tools/perf/builtin-bench.c | 1 +
5 files changed, 419 insertions(+)

diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt
index 3a6b2e73b2e8..0921a3c67381 100644
--- a/tools/perf/Documentation/perf-bench.txt
+++ b/tools/perf/Documentation/perf-bench.txt
@@ -211,6 +211,9 @@ SUITES FOR 'epoll'
*wait*::
Suite for evaluating concurrent epoll_wait calls.

+*ctl*::
+Suite for evaluating multiple epoll_ctl calls.
+
SEE ALSO
--------
linkperf:perf[1]
diff --git a/tools/perf/bench/Build b/tools/perf/bench/Build
index 2bb79b542d53..e4e321b6f883 100644
--- a/tools/perf/bench/Build
+++ b/tools/perf/bench/Build
@@ -8,6 +8,7 @@ perf-y += futex-requeue.o
perf-y += futex-lock-pi.o

perf-y += epoll-wait.o
+perf-y += epoll-ctl.o

perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-lib.o
perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-asm.o
diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h
index 6e1f091ced96..fddb3ced9db6 100644
--- a/tools/perf/bench/bench.h
+++ b/tools/perf/bench/bench.h
@@ -39,6 +39,7 @@ int bench_futex_requeue(int argc, const char **argv);
int bench_futex_lock_pi(int argc, const char **argv);

int bench_epoll_wait(int argc, const char **argv);
+int bench_epoll_ctl(int argc, const char **argv);

#define BENCH_FORMAT_DEFAULT_STR "default"
#define BENCH_FORMAT_DEFAULT 0
diff --git a/tools/perf/bench/epoll-ctl.c b/tools/perf/bench/epoll-ctl.c
new file mode 100644
index 000000000000..0c0a6e824934
--- /dev/null
+++ b/tools/perf/bench/epoll-ctl.c
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Davidlohr Bueso.
+ *
+ * Benchmark the various operations allowed for epoll_ctl(2).
+ * The idea is to concurrently stress a single epoll instance
+ */
+#ifdef HAVE_EVENTFD
+/* For the CLR_() macros */
+#include <string.h>
+#include <pthread.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+
+#include "../util/stat.h"
+#include <subcmd/parse-options.h>
+#include "bench.h"
+#include "cpumap.h"
+
+#include <err.h>
+
+#define printinfo(fmt, arg...) \
+ do { if (__verbose) printf(fmt, ## arg); } while (0)
+
+static unsigned int nthreads = 0;
+static unsigned int nsecs = 8;
+struct timeval start, end, runtime;
+static bool done, __verbose, randomize;
+
+/*
+ * epoll related shared variables.
+ */
+
+/* Maximum number of nesting allowed inside epoll sets */
+#define EPOLL_MAXNESTS 4
+
+enum {
+ OP_EPOLL_ADD,
+ OP_EPOLL_MOD,
+ OP_EPOLL_DEL,
+ EPOLL_NR_OPS,
+};
+
+static int epollfd;
+static int *epollfdp;
+static bool noaffinity;
+static unsigned int nested = 0;
+
+/* amount of fds to monitor, per thread */
+static unsigned int nfds = 64;
+
+static pthread_mutex_t thread_lock;
+static unsigned int threads_starting;
+static struct stats all_stats[EPOLL_NR_OPS];
+static pthread_cond_t thread_parent, thread_worker;
+
+struct worker {
+ int tid;
+ pthread_t thread;
+ unsigned long ops[EPOLL_NR_OPS];
+ int *fdmap;
+};
+
+static const struct option options[] = {
+ OPT_UINTEGER('t', "threads", &nthreads, "Specify amount of threads"),
+ OPT_UINTEGER('r', "runtime", &nsecs, "Specify runtime (in seconds)"),
+ OPT_UINTEGER('f', "nfds", &nfds, "Specify amount of file descriptors to monitor for each thread"),
+ OPT_BOOLEAN( 'n', "noaffinity", &noaffinity, "Disables CPU affinity"),
+ OPT_UINTEGER( 'N', "nested", &nested, "Nesting level epoll hierarchy (default is 0, no nesting)"),
+ OPT_BOOLEAN( 'R', "randomize", &randomize, "Perform random operations on random fds"),
+ OPT_BOOLEAN( 'v', "verbose", &__verbose, "Verbose mode"),
+ OPT_END()
+};
+
+static const char * const bench_epoll_ctl_usage[] = {
+ "perf bench epoll ctl <options>",
+ NULL
+};
+
+static void toggle_done(int sig __maybe_unused,
+ siginfo_t *info __maybe_unused,
+ void *uc __maybe_unused)
+{
+ /* inform all threads that we're done for the day */
+ done = true;
+ gettimeofday(&end, NULL);
+ timersub(&end, &start, &runtime);
+}
+
+static void nest_epollfd(void)
+{
+ unsigned int i;
+ struct epoll_event ev;
+
+ if (nested > EPOLL_MAXNESTS)
+ nested = EPOLL_MAXNESTS;
+ printinfo("Nesting level(s): %d\n", nested);
+
+ epollfdp = calloc(nested, sizeof(int));
+ if (!epollfd)
+ err(EXIT_FAILURE, "calloc");
+
+ for (i = 0; i < nested; i++) {
+ epollfdp[i] = epoll_create(1);
+ if (epollfd < 0)
+ err(EXIT_FAILURE, "epoll_create");
+ }
+
+ ev.events = EPOLLHUP; /* anything */
+ ev.data.u64 = i; /* any number */
+
+ for (i = nested - 1; i; i--) {
+ if (epoll_ctl(epollfdp[i - 1], EPOLL_CTL_ADD,
+ epollfdp[i], &ev) < 0)
+ err(EXIT_FAILURE, "epoll_ctl");
+ }
+
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, *epollfdp, &ev) < 0)
+ err(EXIT_FAILURE, "epoll_ctl");
+}
+
+static inline void do_epoll_op(struct worker *w, int op, int fd)
+{
+ int error;
+ struct epoll_event ev;
+
+ ev.events = EPOLLIN;
+ ev.data.u64 = fd;
+
+ switch (op) {
+ case OP_EPOLL_ADD:
+ error = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
+ break;
+ case OP_EPOLL_MOD:
+ ev.events = EPOLLOUT;
+ error = epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
+ break;
+ case OP_EPOLL_DEL:
+ error = epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
+ break;
+ default:
+ error = 1;
+ break;
+ }
+
+ if (!error)
+ w->ops[op]++;
+}
+
+static inline void do_random_epoll_op(struct worker *w)
+{
+ unsigned long rnd1 = random(), rnd2 = random();
+ int op, fd;
+
+ fd = w->fdmap[rnd1 % nfds];
+ op = rnd2 % EPOLL_NR_OPS;
+
+ do_epoll_op(w, op, fd);
+}
+
+static void *workerfn(void *arg)
+{
+ unsigned int i;
+ struct worker *w = (struct worker *) arg;
+ struct timespec ts = { .tv_sec = 0,
+ .tv_nsec = 250 };
+
+ pthread_mutex_lock(&thread_lock);
+ threads_starting--;
+ if (!threads_starting)
+ pthread_cond_signal(&thread_parent);
+ pthread_cond_wait(&thread_worker, &thread_lock);
+ pthread_mutex_unlock(&thread_lock);
+
+ /* Let 'em loose */
+ do {
+ /* random */
+ if (randomize) {
+ do_random_epoll_op(w);
+ } else {
+ for (i = 0; i < nfds; i++) {
+ do_epoll_op(w, OP_EPOLL_ADD, w->fdmap[i]);
+ do_epoll_op(w, OP_EPOLL_MOD, w->fdmap[i]);
+ do_epoll_op(w, OP_EPOLL_DEL, w->fdmap[i]);
+ }
+ }
+
+ nanosleep(&ts, NULL);
+ } while (!done);
+
+ return NULL;
+}
+
+static void init_fdmaps(struct worker *w, int pct)
+{
+ unsigned int i;
+ int inc;
+ struct epoll_event ev;
+
+ if (!pct)
+ return;
+
+ inc = 100/pct;
+ for (i = 0; i < nfds; i+=inc) {
+ ev.data.fd = w->fdmap[i];
+ ev.events = EPOLLIN;
+
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, w->fdmap[i], &ev) < 0)
+ err(EXIT_FAILURE, "epoll_ct");
+ }
+}
+
+static int do_threads(struct worker *worker, struct cpu_map *cpu)
+{
+ pthread_attr_t thread_attr, *attrp = NULL;
+ cpu_set_t cpuset;
+ unsigned int i, j;
+ int ret;
+
+ if (!noaffinity)
+ pthread_attr_init(&thread_attr);
+
+ for (i = 0; i < nthreads; i++) {
+ struct worker *w = &worker[i];
+
+ w->tid = i;
+ w->fdmap = calloc(nfds, sizeof(int));
+ if (!w->fdmap)
+ return 1;
+
+ for (j = 0; j < nfds; j++) {
+ w->fdmap[j] = eventfd(0, EFD_NONBLOCK);
+ if (w->fdmap[j] < 0)
+ err(EXIT_FAILURE, "eventfd");
+ }
+
+ /*
+ * Lets add 50% of the fdmap to the epoll instance, and
+ * do it before any threads are started; otherwise there is
+ * an initial bias of the call failing (mod and del ops).
+ */
+ if (randomize)
+ init_fdmaps(w, 50);
+
+ if (!noaffinity) {
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpu->map[i % cpu->nr], &cpuset);
+
+ ret = pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpuset);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_attr_setaffinity_np");
+
+ attrp = &thread_attr;
+ }
+
+ ret = pthread_create(&w->thread, attrp, workerfn,
+ (void *)(struct worker *) w);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_create");
+ }
+
+ if (!noaffinity)
+ pthread_attr_destroy(&thread_attr);
+
+ return ret;
+}
+
+static void print_summary(void)
+{
+ int i;
+ unsigned long avg[EPOLL_NR_OPS];
+ double stddev[EPOLL_NR_OPS];
+
+ for (i = 0; i < EPOLL_NR_OPS; i++) {
+ avg[i] = avg_stats(&all_stats[i]);
+ stddev[i] = stddev_stats(&all_stats[i]);
+ }
+
+ printf("\nAveraged %ld ADD operations (+- %.2f%%)\n",
+ avg[OP_EPOLL_ADD], rel_stddev_stats(stddev[OP_EPOLL_ADD],
+ avg[OP_EPOLL_ADD]));
+ printf("Averaged %ld MOD operations (+- %.2f%%)\n",
+ avg[OP_EPOLL_MOD], rel_stddev_stats(stddev[OP_EPOLL_MOD],
+ avg[OP_EPOLL_MOD]));
+ printf("Averaged %ld DEL operations (+- %.2f%%)\n",
+ avg[OP_EPOLL_DEL], rel_stddev_stats(stddev[OP_EPOLL_DEL],
+ avg[OP_EPOLL_DEL]));
+}
+
+int bench_epoll_ctl(int argc, const char **argv)
+{
+ int j, ret = 0;
+ struct sigaction act;
+ struct worker *worker = NULL;
+ struct cpu_map *cpu;
+ struct rlimit rl, prevrl;
+ unsigned int i;
+
+ argc = parse_options(argc, argv, options, bench_epoll_ctl_usage, 0);
+ if (argc) {
+ usage_with_options(bench_epoll_ctl_usage, options);
+ exit(EXIT_FAILURE);
+ }
+
+ sigfillset(&act.sa_mask);
+ act.sa_sigaction = toggle_done;
+ sigaction(SIGINT, &act, NULL);
+
+ cpu = cpu_map__new(NULL);
+ if (!cpu)
+ goto errmem;
+
+ /* a single, main epoll instance */
+ epollfd = epoll_create(1);
+ if (epollfd < 0)
+ err(EXIT_FAILURE, "epoll_create");
+
+ /*
+ * Deal with nested epolls, if any.
+ */
+ if (nested)
+ nest_epollfd();
+
+ /* default to the number of CPUs */
+ if (!nthreads)
+ nthreads = cpu->nr;
+
+ worker = calloc(nthreads, sizeof(*worker));
+ if (!worker)
+ goto errmem;
+
+ if (getrlimit(RLIMIT_NOFILE, &prevrl))
+ err(EXIT_FAILURE, "getrlimit");
+ rl.rlim_cur = rl.rlim_max = nfds * nthreads * 2 + 50;
+ printinfo("Setting RLIMIT_NOFILE rlimit from %" PRIu64 " to: %" PRIu64 "\n",
+ (uint64_t)prevrl.rlim_max, (uint64_t)rl.rlim_max);
+ if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
+ err(EXIT_FAILURE, "setrlimit");
+
+ printf("Run summary [PID %d]: %d threads doing epoll_ctl ops "
+ "%d file-descriptors for %d secs.\n\n",
+ getpid(), nthreads, nfds, nsecs);
+
+ for (i = 0; i < EPOLL_NR_OPS; i++)
+ init_stats(&all_stats[i]);
+
+ pthread_mutex_init(&thread_lock, NULL);
+ pthread_cond_init(&thread_parent, NULL);
+ pthread_cond_init(&thread_worker, NULL);
+
+ threads_starting = nthreads;
+
+ gettimeofday(&start, NULL);
+
+ do_threads(worker, cpu);
+
+ pthread_mutex_lock(&thread_lock);
+ while (threads_starting)
+ pthread_cond_wait(&thread_parent, &thread_lock);
+ pthread_cond_broadcast(&thread_worker);
+ pthread_mutex_unlock(&thread_lock);
+
+ sleep(nsecs);
+ toggle_done(0, NULL, NULL);
+ printinfo("main thread: toggling done\n");
+
+ for (i = 0; i < nthreads; i++) {
+ ret = pthread_join(worker[i].thread, NULL);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_join");
+ }
+
+ /* cleanup & report results */
+ pthread_cond_destroy(&thread_parent);
+ pthread_cond_destroy(&thread_worker);
+ pthread_mutex_destroy(&thread_lock);
+
+ for (i = 0; i < nthreads; i++) {
+ unsigned long t[EPOLL_NR_OPS];
+
+ for (j = 0; j < EPOLL_NR_OPS; j++) {
+ t[j] = worker[i].ops[j];
+ update_stats(&all_stats[j], t[j]);
+ }
+
+ if (nfds == 1)
+ printf("[thread %2d] fdmap: %p [ add: %04ld; mod: %04ld; del: %04lds ops ]\n",
+ worker[i].tid, &worker[i].fdmap[0],
+ t[OP_EPOLL_ADD], t[OP_EPOLL_MOD], t[OP_EPOLL_DEL]);
+ else
+ printf("[thread %2d] fdmap: %p ... %p [ add: %04ld ops; mod: %04ld ops; del: %04ld ops ]\n",
+ worker[i].tid, &worker[i].fdmap[0],
+ &worker[i].fdmap[nfds-1],
+ t[OP_EPOLL_ADD], t[OP_EPOLL_MOD], t[OP_EPOLL_DEL]);
+ }
+
+ print_summary();
+
+ close(epollfd);
+ return ret;
+errmem:
+ err(EXIT_FAILURE, "calloc");
+}
+#endif // HAVE_EVENTFD
diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c
index 55efd23c3efb..334c77ffc1d9 100644
--- a/tools/perf/builtin-bench.c
+++ b/tools/perf/builtin-bench.c
@@ -71,6 +71,7 @@ static struct bench futex_benchmarks[] = {
#ifdef HAVE_EVENTFD
static struct bench epoll_benchmarks[] = {
{ "wait", "Benchmark epoll concurrent epoll_waits", bench_epoll_wait },
+ { "ctl", "Benchmark epoll concurrent epoll_ctls", bench_epoll_ctl },
{ "all", "Run all futex benchmarks", NULL },
{ NULL, NULL, NULL }
};