Add a simple perf test for userfaultfd missing mode, on private anon only. It mostly only tests the messaging, so memory type / fault type may not that much yet. Signed-off-by: Peter Xu <peterx@xxxxxxxxxx> --- tools/testing/selftests/mm/Makefile | 2 + tools/testing/selftests/mm/uffd-common.c | 18 ++ tools/testing/selftests/mm/uffd-common.h | 1 + tools/testing/selftests/mm/uffd-perf.c | 207 +++++++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 tools/testing/selftests/mm/uffd-perf.c diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile index 6a9fc5693145..acb22517d37e 100644 --- a/tools/testing/selftests/mm/Makefile +++ b/tools/testing/selftests/mm/Makefile @@ -64,6 +64,7 @@ TEST_GEN_FILES += thuge-gen TEST_GEN_FILES += transhuge-stress TEST_GEN_FILES += uffd-stress TEST_GEN_FILES += uffd-unit-tests +TEST_GEN_FILES += uffd-perf TEST_GEN_FILES += split_huge_page_test TEST_GEN_FILES += ksm_tests TEST_GEN_FILES += ksm_functional_tests @@ -120,6 +121,7 @@ $(TEST_GEN_FILES): vm_util.c $(OUTPUT)/uffd-stress: uffd-common.c $(OUTPUT)/uffd-unit-tests: uffd-common.c +$(OUTPUT)/uffd-perf: uffd-common.c ifeq ($(ARCH),x86_64) BINARIES_32 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_32)) diff --git a/tools/testing/selftests/mm/uffd-common.c b/tools/testing/selftests/mm/uffd-common.c index 851284395b29..afbf2f7add56 100644 --- a/tools/testing/selftests/mm/uffd-common.c +++ b/tools/testing/selftests/mm/uffd-common.c @@ -725,3 +725,21 @@ int uffd_get_features(uint64_t *features) return 0; } + +uint64_t get_usec(void) +{ + uint64_t val = 0; + struct timespec t; + int ret = clock_gettime(CLOCK_MONOTONIC, &t); + + if (ret == -1) { + perror("clock_gettime() failed"); + /* should never happen */ + exit(-1); + } + + val = t.tv_nsec / 1000; /* ns -> us */ + val += t.tv_sec * 1000000; /* s -> us */ + + return val; +} diff --git a/tools/testing/selftests/mm/uffd-common.h b/tools/testing/selftests/mm/uffd-common.h index 9d66ad5c52cb..4273201ae19f 100644 --- a/tools/testing/selftests/mm/uffd-common.h +++ b/tools/testing/selftests/mm/uffd-common.h @@ -123,6 +123,7 @@ int uffd_open_dev(unsigned int flags); int uffd_open_sys(unsigned int flags); int uffd_open(unsigned int flags); int uffd_get_features(uint64_t *features); +uint64_t get_usec(void); #define TEST_ANON 1 #define TEST_HUGETLB 2 diff --git a/tools/testing/selftests/mm/uffd-perf.c b/tools/testing/selftests/mm/uffd-perf.c new file mode 100644 index 000000000000..eda99718311a --- /dev/null +++ b/tools/testing/selftests/mm/uffd-perf.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Userfaultfd performance tests. + * + * Copyright (C) 2023 Red Hat, Inc. + */ + +#include "uffd-common.h" + +#ifdef __NR_userfaultfd + +#define DEF_MEM_SIZE_MB (512) +#define MB(x) ((x) * 1024 * 1024) +#define DEF_N_TESTS 5 + +static volatile bool perf_test_started; +static unsigned int n_uffd_threads, n_worker_threads; +static uint64_t nr_pages_per_worker; +static unsigned long n_tests = DEF_N_TESTS; + +static void setup_env(unsigned long mem_size_mb) +{ + /* Test private anon only for now */ + map_shared = false; + uffd_test_ops = &anon_uffd_test_ops; + page_size = psize(); + nr_cpus = n_uffd_threads; + nr_pages = MB(mem_size_mb) / page_size; + nr_pages_per_worker = nr_pages / n_worker_threads; + if (nr_pages_per_worker == 0) + err("each worker should at least own one page"); +} + +void *worker_fn(void *opaque) +{ + unsigned long i = (unsigned long) opaque; + unsigned long page_nr, start_nr, end_nr; + int v = 0; + + start_nr = i * nr_pages_per_worker; + end_nr = (i + 1) * nr_pages_per_worker; + + while (!perf_test_started); + + for (page_nr = start_nr; page_nr < end_nr; page_nr++) + v += *(volatile int *)(area_dst + page_nr * page_size); + + return NULL; +} + +static uint64_t run_perf(uint64_t mem_size_mb, bool poll) +{ + pthread_t worker_threads[n_worker_threads]; + pthread_t uffd_threads[n_uffd_threads]; + const char *errmsg = NULL; + struct uffd_args *args; + uint64_t start, end; + int i, ret; + + if (uffd_test_ctx_init(0, &errmsg)) + err("%s", errmsg); + + /* + * By default, uffd is opened with NONBLOCK mode; use block mode + * when test read() + */ + if (!poll) { + int flags = fcntl(uffd, F_GETFL); + + if (flags < 0) + err("fcntl(F_GETFL) failed"); + + if (flags & O_NONBLOCK) + flags &= ~O_NONBLOCK; + + if (fcntl(uffd, F_SETFL, flags)) + err("fcntl(F_SETFL) failed"); + } + + ret = uffd_register(uffd, area_dst, MB(mem_size_mb), + true, false, false); + if (ret) + err("uffd_register() failed"); + + args = calloc(nr_cpus, sizeof(struct uffd_args)); + if (!args) + err("calloc()"); + + for (i = 0; i < n_uffd_threads; i++) { + args[i].cpu = i; + uffd_fault_thread_create(&uffd_threads[i], NULL, + &args[i], poll); + } + + for (i = 0; i < n_worker_threads; i++) { + if (pthread_create(&worker_threads[i], NULL, + worker_fn, (void *)(uintptr_t)i)) + err("create uffd threads"); + } + + start = get_usec(); + perf_test_started = true; + for (i = 0; i < n_worker_threads; i++) + pthread_join(worker_threads[i], NULL); + end = get_usec(); + + for (i = 0; i < n_uffd_threads; i++) { + struct uffd_args *p = &args[i]; + + uffd_fault_thread_join(uffd_threads[i], i, poll); + + assert(p->wp_faults == 0 && p->minor_faults == 0); + } + + free(args); + + ret = uffd_unregister(uffd, area_dst, MB(mem_size_mb)); + if (ret) + err("uffd_unregister() failed"); + + return end - start; +} + +static void usage(const char *prog) +{ + printf("usage: %s <options>\n", prog); + puts(""); + printf(" -m: size of memory to test (in MB, default: %u)\n", + DEF_MEM_SIZE_MB); + puts(" -p: use poll() (the default)"); + puts(" -r: use read()"); + printf(" -t: test rounds (default: %u)\n", DEF_N_TESTS); + puts(" -u: number of uffd threads (default: n_cpus)"); + puts(" -w: number of worker threads (default: n_cpus)"); + puts(""); + exit(KSFT_FAIL); +} + +int main(int argc, char *argv[]) +{ + unsigned long mem_size_mb = DEF_MEM_SIZE_MB; + uint64_t result, sum = 0; + bool use_poll = true; + int opt, count; + + n_uffd_threads = n_worker_threads = sysconf(_SC_NPROCESSORS_ONLN); + + while ((opt = getopt(argc, argv, "hm:prt:u:w:")) != -1) { + switch (opt) { + case 'm': + mem_size_mb = strtoul(optarg, NULL, 10); + break; + case 'p': + use_poll = true; + break; + case 'r': + use_poll = false; + break; + case 't': + n_tests = strtoul(optarg, NULL, 10); + break; + case 'u': + n_uffd_threads = strtoul(optarg, NULL, 10); + break; + case 'w': + n_worker_threads = strtoul(optarg, NULL, 10); + break; + case 'h': + default: + /* Unknown */ + usage(argv[0]); + break; + } + } + + setup_env(mem_size_mb); + + printf("Message mode: \t\t%s\n", use_poll ? "poll" : "read"); + printf("Mem size: \t\t%lu (MB)\n", mem_size_mb); + printf("Uffd threads: \t\t%u\n", n_uffd_threads); + printf("Worker threads: \t%u\n", n_worker_threads); + printf("Test rounds: \t\t%lu\n", n_tests); + printf("Time used (us): \t"); + + for (count = 0; count < n_tests; count++) { + result = run_perf(mem_size_mb, use_poll); + sum += result; + printf("%" PRIu64 ", ", result); + fflush(stdout); + } + printf("\b\b \n"); + printf("Average (us): \t\t%"PRIu64"\n", sum / n_tests); + + return KSFT_PASS; +} + +#else /* __NR_userfaultfd */ + +#warning "missing __NR_userfaultfd definition" + +int main(void) +{ + printf("Skipping %s (missing __NR_userfaultfd)\n", __file__); + return KSFT_SKIP; +} + +#endif /* __NR_userfaultfd */ -- 2.41.0