All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
From: Peter Oskolkov <posk@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	Thomas Gleixner <tglx@linutronix.de>,
	linux-kernel@vger.kernel.org, linux-api@vger.kernel.org
Cc: Paul Turner <pjt@google.com>, Ben Segall <bsegall@google.com>,
	Peter Oskolkov <posk@google.com>, Peter Oskolkov <posk@posk.io>,
	Joel Fernandes <joel@joelfernandes.org>,
	Andrew Morton <akpm@linux-foundation.org>,
	Andrei Vagin <avagin@google.com>,
	Jim Newsome <jnewsome@torproject.org>
Subject: [RFC PATCH v0.1 6/9] selftests/umcg: add UMCG core API selftest
Date: Thu, 20 May 2021 11:36:11 -0700	[thread overview]
Message-ID: <20210520183614.1227046-7-posk@google.com> (raw)
In-Reply-To: <20210520183614.1227046-1-posk@google.com>

Add UMCG core API selftests. In particular, test that
umcg_wait/umcg_wake/umcg_swap behave correctly when racing
with each other.

Signed-off-by: Peter Oskolkov <posk@google.com>
---
 tools/testing/selftests/umcg/.gitignore       |   2 +
 tools/testing/selftests/umcg/Makefile         |  13 +
 tools/testing/selftests/umcg/umcg_core_test.c | 347 ++++++++++++++++++
 3 files changed, 362 insertions(+)
 create mode 100644 tools/testing/selftests/umcg/.gitignore
 create mode 100644 tools/testing/selftests/umcg/Makefile
 create mode 100644 tools/testing/selftests/umcg/umcg_core_test.c

diff --git a/tools/testing/selftests/umcg/.gitignore b/tools/testing/selftests/umcg/.gitignore
new file mode 100644
index 000000000000..89cca24e5907
--- /dev/null
+++ b/tools/testing/selftests/umcg/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+umcg_core_test
diff --git a/tools/testing/selftests/umcg/Makefile b/tools/testing/selftests/umcg/Makefile
new file mode 100644
index 000000000000..b151098e2ed1
--- /dev/null
+++ b/tools/testing/selftests/umcg/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+TOOLSDIR := $(abspath ../../..)
+LIBUMCGDIR := $(TOOLSDIR)/lib/umcg
+
+CFLAGS += -g -O0 -I$(LIBUMCGDIR) -I$(TOOLSDIR)/include/ -I../../../../usr/include/
+LDLIBS += -lpthread -static
+
+TEST_GEN_PROGS := umcg_core_test
+
+include ../lib.mk
+
+$(OUTPUT)/umcg_core_test: umcg_core_test.c $(LIBUMCGDIR)/libumcg.c
diff --git a/tools/testing/selftests/umcg/umcg_core_test.c b/tools/testing/selftests/umcg/umcg_core_test.c
new file mode 100644
index 000000000000..4dc20131ace7
--- /dev/null
+++ b/tools/testing/selftests/umcg/umcg_core_test.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include "libumcg.h"
+
+#include <pthread.h>
+#include <stdatomic.h>
+
+#include "../kselftest_harness.h"
+
+#define CHECK_CONFIG()						\
+{								\
+	int ret = sys_umcg_api_version(1, 0);			\
+	if (ret == -1 && errno == ENOSYS)			\
+		SKIP(return, "CONFIG_UMCG not set");	\
+}
+
+TEST(umcg_api_version) {
+	CHECK_CONFIG();
+	ASSERT_EQ(0, sys_umcg_api_version(1, 0));
+	ASSERT_EQ(1, sys_umcg_api_version(1234, 0));
+}
+
+/* Test that forked children of UMCG enabled tasks are not UMCG enabled. */
+TEST(register_and_fork) {
+	CHECK_CONFIG();
+	pid_t pid;
+	int wstatus;
+	umcg_tid utid;
+
+	/* umcg_unregister should fail without registering earlier. */
+	ASSERT_NE(0, umcg_unregister_task());
+
+	utid = umcg_register_core_task(0);
+	ASSERT_TRUE(utid != UMCG_NONE);
+
+	pid = fork();
+	if (pid == 0) {
+		/* This is child. umcg_unregister_task() should fail. */
+		if (!umcg_unregister_task()) {
+			fprintf(stderr, "umcg_unregister_task() succeeded in "
+					"the forked child.\n");
+			exit(1);
+		}
+		exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &wstatus, 0));
+	ASSERT_TRUE(WIFEXITED(wstatus));
+	ASSERT_EQ(0, WEXITSTATUS(wstatus));
+	ASSERT_EQ(0, umcg_unregister_task());
+}
+
+struct test_waiter_args {
+	umcg_tid	utid;
+	bool		stop;
+	bool		waiting;
+};
+
+/* Thread FN for the test waiter: calls umcg_wait() in a loop until stopped. */
+static void *test_waiter_threadfn(void *arg)
+{
+	struct test_waiter_args *args = (struct test_waiter_args *)arg;
+	uint64_t counter = 0;
+
+	atomic_store_explicit(&args->utid, umcg_register_core_task(0),
+			memory_order_relaxed);
+	if (!args->utid) {
+		fprintf(stderr, "umcg_register_core_task failed: %d.\n", errno);
+		exit(1);
+	}
+
+	while (!atomic_load_explicit(&args->stop, memory_order_seq_cst)) {
+		bool expected = false;
+
+		if (!atomic_compare_exchange_strong_explicit(&args->waiting,
+					&expected, true,
+					memory_order_seq_cst,
+					memory_order_seq_cst)) {
+			fprintf(stderr, "Failed to set waiting flag.\n");
+			exit(1);
+		}
+
+		++counter;
+		if (counter % 5 == 0)
+			usleep(1);  /* Trigger a race with ucmg_wake(). */
+
+		if (umcg_wait(NULL)) {
+			fprintf(stderr, "umcg_wait failed: %d.\n", errno);
+			exit(1);
+		}
+	}
+
+	if (umcg_unregister_task()) {
+		fprintf(stderr, "umcg_register_core_task failed: %d.\n", errno);
+		exit(1);
+	}
+
+	return (void *)counter;
+}
+
+/* Test wake/wait pair racing with each other. */
+TEST(umcg_wake_wait) {
+	CHECK_CONFIG();
+	struct test_waiter_args args;
+	const int steps = 10000;
+	bool expected = true;
+	void *result;
+	pthread_t t;
+	int ret;
+
+	args.utid = UMCG_NONE;
+	args.stop = false;
+	args.waiting = false;
+
+	ASSERT_EQ(0, pthread_create(&t, NULL, &test_waiter_threadfn, &args));
+
+	while (!atomic_load_explicit(&args.utid, memory_order_relaxed))
+		;
+
+	for (int step = 0; step < steps; ++step) {
+		/* Spin until the waiter indicates it is going to wait. */
+		while (!atomic_compare_exchange_weak_explicit(&args.waiting,
+					&expected, false,
+					memory_order_seq_cst,
+					memory_order_seq_cst)) {
+			expected = true;
+		}
+
+		ASSERT_EQ(0, umcg_wake(args.utid));
+	}
+
+	/* Carefully shut down. */
+	expected = true;
+	while (!atomic_compare_exchange_weak_explicit(&args.waiting, &expected,
+			false, memory_order_seq_cst, memory_order_seq_cst)) {
+		expected = true;
+	}
+	atomic_store_explicit(&args.stop, true, memory_order_seq_cst);
+	ret = umcg_wake(args.utid);
+
+	/* If the worker immediately exits upon wake, we may get ESRCH. */
+	ASSERT_TRUE((ret == 0) || (errno == ESRCH));
+
+	ASSERT_EQ(0, pthread_join(t, &result));
+	ASSERT_EQ(steps + 1, (uint64_t)result);
+}
+
+struct test_ping_pong_args {
+	bool		ping;  /* Is this worker doing pings or pongs? */
+	umcg_tid	utid_self;
+	umcg_tid	utid_peer;
+	int		steps;
+	bool		use_swap;  /* Use umcg_swap or wake/wait. */
+	bool		payload;   /* call gettid() if true at each iteration. */
+
+	/*
+	 * It is not allowed to wake a task that has a wakeup queued, so
+	 * normally the test "softly" synchronizes ping and pong tasks so
+	 * that pong calls umcg_wait() to wait for the first ping.
+	 *
+	 * However, it is allowed to do mutual umcg_swap(), so in the
+	 * test flavor when both ping and pong tasks use swaps we also
+	 * run the test without pong waiting for the initial ping.
+	 */
+	bool		pong_waits;
+};
+
+/* Thread FN for ping-pong workers. */
+static void *test_ping_pong_threadfn(void *arg)
+{
+	struct test_ping_pong_args *args = (struct test_ping_pong_args *)arg;
+	struct timespec start, stop;
+	int counter;
+
+	atomic_store_explicit(&args->utid_self, umcg_register_core_task(0),
+			memory_order_relaxed);
+	if (!args->utid_self) {
+		fprintf(stderr, "umcg_register_core_task failed: %d.\n", errno);
+		exit(1);
+	}
+
+	while (!atomic_load_explicit(&args->utid_peer, memory_order_acquire))
+		;
+
+	if (args->pong_waits && !args->ping) {
+		/* This is pong: we sleep first. */
+		if (umcg_wait(NULL)) {
+			fprintf(stderr, "umcg_wait failed: %d.\n", errno);
+			exit(1);
+		}
+	}
+
+	if (args->ping) {  /* The "ping" measures the running time. */
+		if (clock_gettime(CLOCK_MONOTONIC, &start)) {
+			fprintf(stderr, "clock_gettime() failed.\n");
+			exit(1);
+		}
+	}
+
+	for (counter = 0; counter < args->steps; ++counter) {
+		int ret;
+
+		if (args->payload)
+			gettid();
+
+		if (args->use_swap) {
+			ret = umcg_swap(args->utid_peer, NULL);
+		} else {
+			ret = umcg_wake(args->utid_peer);
+			if (!ret)
+				ret = umcg_wait(NULL);
+		}
+
+		if (ret) {
+			if (args->use_swap)
+				fprintf(stderr, "umcg_swap failed: %d.\n", errno);
+			else
+				fprintf(stderr, "umcg_wake/wait failed: %d.\n", errno);
+			exit(1);
+		}
+	}
+
+	if (args->ping) {
+		uint64_t duration;
+
+		if (clock_gettime(CLOCK_MONOTONIC, &stop)) {
+			fprintf(stderr, "clock_gettime() failed.\n");
+			exit(1);
+		}
+
+		duration = (stop.tv_sec - start.tv_sec) * 1000000000LL +
+		stop.tv_nsec - start.tv_nsec;
+		printf("completed %d ping-pong iterations in %lu ns: "
+				"%lu ns per context switch\n",
+			args->steps, duration, duration / (args->steps * 2));
+	}
+
+	if (args->pong_waits && args->ping) {
+		/* This is ping: we wake pong at the end. */
+		if (umcg_wake(args->utid_peer)) {
+			fprintf(stderr, "umcg_wake failed: %d.\n", errno);
+			exit(1);
+		}
+	}
+
+	if (umcg_unregister_task()) {
+		fprintf(stderr, "umcg_unregister_task failed: %d.\n", errno);
+		exit(1);
+	}
+
+	return NULL;
+}
+
+enum ping_pong_flavor {
+	NO_SWAPS,	/* Use wake/wait pairs on both sides. */
+	ONE_SWAP,	/* Use wake/wait on one side and swap on the other. */
+	ALL_SWAPS	/* Use swaps on both sides. */
+};
+
+static void test_ping_pong_flavored(enum ping_pong_flavor flavor,
+		bool pong_waits, bool payload)
+{
+	struct test_ping_pong_args ping, pong;
+	pthread_t ping_t, pong_t;
+	const int STEPS = 100000;
+
+	ping.ping = true;
+	ping.utid_self = UMCG_NONE;
+	ping.utid_peer = UMCG_NONE;
+	ping.steps = STEPS;
+	ping.pong_waits = pong_waits;
+	ping.payload = payload;
+
+	pong.ping = false;
+	pong.utid_self = UMCG_NONE;
+	pong.utid_peer = UMCG_NONE;
+	pong.steps = STEPS;
+	pong.pong_waits = pong_waits;
+	pong.payload = payload;
+
+	switch (flavor) {
+	case NO_SWAPS:
+		ping.use_swap = false;
+		pong.use_swap = false;
+		break;
+	case ONE_SWAP:
+		ping.use_swap = true;
+		pong.use_swap = false;
+		break;
+	case ALL_SWAPS:
+		ping.use_swap = true;
+		pong.use_swap = true;
+		break;
+	default:
+		fprintf(stderr, "Unknown ping/pong flavor.\n");
+		exit(1);
+	}
+
+	if (pthread_create(&ping_t, NULL, &test_ping_pong_threadfn, &ping)) {
+		fprintf(stderr, "pthread_create(ping) failed.\n");
+		exit(1);
+	}
+
+	while (!atomic_load_explicit(&ping.utid_self, memory_order_relaxed))
+		;
+	pong.utid_peer = ping.utid_self;
+
+	if (pthread_create(&pong_t, NULL, &test_ping_pong_threadfn, &pong)) {
+		fprintf(stderr, "pthread_create(pong) failed.\n");
+		exit(1);
+	}
+
+	while (!atomic_load_explicit(&pong.utid_self, memory_order_relaxed))
+		;
+	atomic_store_explicit(&ping.utid_peer, pong.utid_self,
+			memory_order_relaxed);
+
+	pthread_join(ping_t, NULL);
+	pthread_join(pong_t, NULL);
+}
+
+TEST(umcg_ping_pong_no_swaps_nop) {
+	CHECK_CONFIG();
+	test_ping_pong_flavored(NO_SWAPS, true, false);
+}
+TEST(umcg_ping_pong_one_swap_nop) {
+	CHECK_CONFIG();
+	test_ping_pong_flavored(ONE_SWAP, true, false);
+}
+TEST(umcg_ping_pong_all_swaps_nop) {
+	CHECK_CONFIG();
+	test_ping_pong_flavored(ALL_SWAPS, true, false);
+}
+TEST(umcg_ping_pong_all_swaps_loose_nop) {
+	CHECK_CONFIG();
+	test_ping_pong_flavored(ALL_SWAPS, false, false);
+}
+TEST(umcg_ping_pong_no_swaps_payload) {
+	CHECK_CONFIG();
+	test_ping_pong_flavored(NO_SWAPS, true, true);
+}
+TEST(umcg_ping_pong_all_swaps_payload) {
+	CHECK_CONFIG();
+	test_ping_pong_flavored(ALL_SWAPS, true, true);
+}
+
+TEST_HARNESS_MAIN
-- 
2.31.1.818.g46aad6cb9e-goog


  parent reply	other threads:[~2021-05-20 18:36 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-05-20 18:36 [RFC PATCH v0.1 0/9] UMCG early preview/RFC patchset Peter Oskolkov
2021-05-20 18:36 ` [RFC PATCH v0.1 1/9] sched/umcg: add UMCG syscall stubs and CONFIG_UMCG Peter Oskolkov
2021-05-22 18:40   ` kernel test robot
2021-05-22 21:49   ` kernel test robot
2021-05-20 18:36 ` [RFC PATCH v0.1 2/9] sched/umcg: add uapi/linux/umcg.h and sched/umcg.c Peter Oskolkov
2021-05-20 18:36 ` [RFC PATCH v0.1 3/9] sched: add WF_CURRENT_CPU and externise ttwu Peter Oskolkov
2021-05-20 18:36 ` [RFC PATCH v0.1 4/9] sched/umcg: implement core UMCG API Peter Oskolkov
2021-05-21 19:06   ` Andrei Vagin
2021-05-21 21:31     ` Jann Horn
2021-05-21 22:03       ` Peter Oskolkov
2021-05-21 19:32   ` Andy Lutomirski
2021-05-21 22:01     ` Peter Oskolkov
2021-05-21 21:33   ` Jann Horn
2021-06-09 13:01     ` Peter Zijlstra
2021-05-20 18:36 ` [RFC PATCH v0.1 5/9] lib/umcg: implement UMCG core API for userspace Peter Oskolkov
2021-05-20 18:36 ` Peter Oskolkov [this message]
2021-05-20 18:36 ` [RFC PATCH v0.1 7/9] sched/umcg: add UMCG server/worker API (early RFC) Peter Oskolkov
2021-05-21 20:17   ` Andrei Vagin
2021-05-22 18:29   ` kernel test robot
2021-05-22 19:34   ` kernel test robot
2021-05-22 20:19   ` kernel test robot
2021-05-20 18:36 ` [RFC PATCH v0.1 8/9] lib/umcg: " Peter Oskolkov
2021-05-20 18:36 ` [RFC PATCH v0.1 9/9] selftests/umcg: add UMCG server/worker API selftest Peter Oskolkov
2021-05-20 21:17 ` [RFC PATCH v0.1 0/9] UMCG early preview/RFC patchset Jonathan Corbet
2021-05-20 21:38   ` Peter Oskolkov
2021-05-21  0:15     ` Randy Dunlap
2021-05-21  8:04       ` Peter Zijlstra
2021-05-21 15:08     ` Jonathan Corbet
2021-05-21 16:03       ` Peter Oskolkov
2021-05-21 19:17         ` Jonathan Corbet
2021-05-27  0:06           ` Peter Oskolkov
2021-05-27 15:41             ` Jonathan Corbet
     [not found] ` <CAEWA0a72SvpcuN4ov=98T3uWtExPCr7BQePOgjkqD1ofWKEASw@mail.gmail.com>
2021-05-21 19:13   ` Peter Oskolkov
2021-05-21 23:08     ` Jann Horn
2021-06-09 12:54 ` Peter Zijlstra
2021-06-09 20:18   ` Peter Oskolkov
2021-06-10 18:02     ` Peter Zijlstra
2021-06-10 20:06       ` Peter Oskolkov
2021-07-07 17:45       ` Thierry Delisle
2021-07-08 21:44         ` Peter Oskolkov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210520183614.1227046-7-posk@google.com \
    --to=posk@google.com \
    --cc=akpm@linux-foundation.org \
    --cc=avagin@google.com \
    --cc=bsegall@google.com \
    --cc=jnewsome@torproject.org \
    --cc=joel@joelfernandes.org \
    --cc=linux-api@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mingo@redhat.com \
    --cc=peterz@infradead.org \
    --cc=pjt@google.com \
    --cc=posk@posk.io \
    --cc=tglx@linutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.