|  | /* | 
|  | * rcutorture.c: simple user-level performance/stress test of RCU. | 
|  | * | 
|  | * Usage: | 
|  | *     ./rcu <nreaders> rperf [ <seconds> ] | 
|  | *         Run a read-side performance test with the specified | 
|  | *         number of readers for <seconds> seconds. | 
|  | *     ./rcu <nupdaters> uperf [ <seconds> ] | 
|  | *         Run an update-side performance test with the specified | 
|  | *         number of updaters and specified duration. | 
|  | *     ./rcu <nreaders> perf [ <seconds> ] | 
|  | *         Run a combined read/update performance test with the specified | 
|  | *         number of readers and one updater and specified duration. | 
|  | * | 
|  | * The above tests produce output as follows: | 
|  | * | 
|  | * n_reads: 46008000  n_updates: 146026  nreaders: 2  nupdaters: 1 duration: 1 | 
|  | * ns/read: 43.4707  ns/update: 6848.1 | 
|  | * | 
|  | * The first line lists the total number of RCU reads and updates executed | 
|  | * during the test, the number of reader threads, the number of updater | 
|  | * threads, and the duration of the test in seconds.  The second line | 
|  | * lists the average duration of each type of operation in nanoseconds, | 
|  | * or "nan" if the corresponding type of operation was not performed. | 
|  | * | 
|  | *     ./rcu <nreaders> stress [ <seconds> ] | 
|  | *         Run a stress test with the specified number of readers and | 
|  | *         one updater. | 
|  | * | 
|  | * This test produces output as follows: | 
|  | * | 
|  | * n_reads: 114633217  n_updates: 3903415  n_mberror: 0 | 
|  | * rcu_stress_count: 114618391 14826 0 0 0 0 0 0 0 0 0 | 
|  | * | 
|  | * The first line lists the number of RCU read and update operations | 
|  | * executed, followed by the number of memory-ordering violations | 
|  | * (which will be zero in a correct RCU implementation).  The second | 
|  | * line lists the number of readers observing progressively more stale | 
|  | * data.  A correct RCU implementation will have all but the first two | 
|  | * numbers non-zero. | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of the GNU General Public License as published by | 
|  | * the Free Software Foundation; either version 2 of the License, or | 
|  | * (at your option) any later version. | 
|  | * | 
|  | * This program is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | * GNU General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU General Public License | 
|  | * along with this program. If not, see <https://www.gnu.org/licenses/>. | 
|  | * | 
|  | * Copyright (c) 2008 Paul E. McKenney, IBM Corporation. | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Test variables. | 
|  | */ | 
|  |  | 
|  | #include "qemu/osdep.h" | 
|  | #include "qemu/atomic.h" | 
|  | #include "qemu/rcu.h" | 
|  | #include "qemu/thread.h" | 
|  |  | 
|  | int nthreadsrunning; | 
|  |  | 
|  | #define GOFLAG_INIT 0 | 
|  | #define GOFLAG_RUN  1 | 
|  | #define GOFLAG_STOP 2 | 
|  |  | 
|  | static volatile int goflag = GOFLAG_INIT; | 
|  |  | 
|  | #define RCU_READ_RUN 1000 | 
|  |  | 
|  | #define NR_THREADS 100 | 
|  | static QemuThread threads[NR_THREADS]; | 
|  | static struct rcu_reader_data *data[NR_THREADS]; | 
|  | static int n_threads; | 
|  |  | 
|  | /* | 
|  | * Statistical counts | 
|  | * | 
|  | * These are the sum of local counters at the end of a run. | 
|  | * Updates are protected by a mutex. | 
|  | */ | 
|  | static QemuMutex counts_mutex; | 
|  | long long n_reads = 0LL; | 
|  | long n_updates = 0L; | 
|  |  | 
|  | static void create_thread(void *(*func)(void *)) | 
|  | { | 
|  | if (n_threads >= NR_THREADS) { | 
|  | fprintf(stderr, "Thread limit of %d exceeded!\n", NR_THREADS); | 
|  | exit(-1); | 
|  | } | 
|  | qemu_thread_create(&threads[n_threads], "test", func, &data[n_threads], | 
|  | QEMU_THREAD_JOINABLE); | 
|  | n_threads++; | 
|  | } | 
|  |  | 
|  | static void wait_all_threads(void) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < n_threads; i++) { | 
|  | qemu_thread_join(&threads[i]); | 
|  | } | 
|  | n_threads = 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Performance test. | 
|  | */ | 
|  |  | 
|  | static void *rcu_read_perf_test(void *arg) | 
|  | { | 
|  | int i; | 
|  | long long n_reads_local = 0; | 
|  |  | 
|  | rcu_register_thread(); | 
|  |  | 
|  | *(struct rcu_reader_data **)arg = get_ptr_rcu_reader(); | 
|  | qatomic_inc(&nthreadsrunning); | 
|  | while (goflag == GOFLAG_INIT) { | 
|  | g_usleep(1000); | 
|  | } | 
|  | while (goflag == GOFLAG_RUN) { | 
|  | for (i = 0; i < RCU_READ_RUN; i++) { | 
|  | rcu_read_lock(); | 
|  | rcu_read_unlock(); | 
|  | } | 
|  | n_reads_local += RCU_READ_RUN; | 
|  | } | 
|  | qemu_mutex_lock(&counts_mutex); | 
|  | n_reads += n_reads_local; | 
|  | qemu_mutex_unlock(&counts_mutex); | 
|  |  | 
|  | rcu_unregister_thread(); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static void *rcu_update_perf_test(void *arg) | 
|  | { | 
|  | long long n_updates_local = 0; | 
|  |  | 
|  | rcu_register_thread(); | 
|  |  | 
|  | *(struct rcu_reader_data **)arg = get_ptr_rcu_reader(); | 
|  | qatomic_inc(&nthreadsrunning); | 
|  | while (goflag == GOFLAG_INIT) { | 
|  | g_usleep(1000); | 
|  | } | 
|  | while (goflag == GOFLAG_RUN) { | 
|  | synchronize_rcu(); | 
|  | n_updates_local++; | 
|  | } | 
|  | qemu_mutex_lock(&counts_mutex); | 
|  | n_updates += n_updates_local; | 
|  | qemu_mutex_unlock(&counts_mutex); | 
|  |  | 
|  | rcu_unregister_thread(); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static void perftestinit(void) | 
|  | { | 
|  | nthreadsrunning = 0; | 
|  | } | 
|  |  | 
|  | static void perftestrun(int nthreads, int duration, int nreaders, int nupdaters) | 
|  | { | 
|  | while (qatomic_read(&nthreadsrunning) < nthreads) { | 
|  | g_usleep(1000); | 
|  | } | 
|  | goflag = GOFLAG_RUN; | 
|  | g_usleep(duration * G_USEC_PER_SEC); | 
|  | goflag = GOFLAG_STOP; | 
|  | wait_all_threads(); | 
|  | printf("n_reads: %lld  n_updates: %ld  nreaders: %d  nupdaters: %d duration: %d\n", | 
|  | n_reads, n_updates, nreaders, nupdaters, duration); | 
|  | printf("ns/read: %g  ns/update: %g\n", | 
|  | ((duration * 1000*1000*1000.*(double)nreaders) / | 
|  | (double)n_reads), | 
|  | ((duration * 1000*1000*1000.*(double)nupdaters) / | 
|  | (double)n_updates)); | 
|  | exit(0); | 
|  | } | 
|  |  | 
|  | static void perftest(int nreaders, int duration) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | perftestinit(); | 
|  | for (i = 0; i < nreaders; i++) { | 
|  | create_thread(rcu_read_perf_test); | 
|  | } | 
|  | create_thread(rcu_update_perf_test); | 
|  | perftestrun(i + 1, duration, nreaders, 1); | 
|  | } | 
|  |  | 
|  | static void rperftest(int nreaders, int duration) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | perftestinit(); | 
|  | for (i = 0; i < nreaders; i++) { | 
|  | create_thread(rcu_read_perf_test); | 
|  | } | 
|  | perftestrun(i, duration, nreaders, 0); | 
|  | } | 
|  |  | 
|  | static void uperftest(int nupdaters, int duration) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | perftestinit(); | 
|  | for (i = 0; i < nupdaters; i++) { | 
|  | create_thread(rcu_update_perf_test); | 
|  | } | 
|  | perftestrun(i, duration, 0, nupdaters); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Stress test. | 
|  | */ | 
|  |  | 
|  | #define RCU_STRESS_PIPE_LEN 10 | 
|  |  | 
|  | struct rcu_stress { | 
|  | int age;  /* how many update cycles while not rcu_stress_current */ | 
|  | int mbtest; | 
|  | }; | 
|  |  | 
|  | struct rcu_stress rcu_stress_array[RCU_STRESS_PIPE_LEN] = { { 0 } }; | 
|  | struct rcu_stress *rcu_stress_current; | 
|  | int n_mberror; | 
|  |  | 
|  | /* Updates protected by counts_mutex */ | 
|  | long long rcu_stress_count[RCU_STRESS_PIPE_LEN + 1]; | 
|  |  | 
|  |  | 
|  | static void *rcu_read_stress_test(void *arg) | 
|  | { | 
|  | int i; | 
|  | struct rcu_stress *p; | 
|  | int pc; | 
|  | long long n_reads_local = 0; | 
|  | long long rcu_stress_local[RCU_STRESS_PIPE_LEN + 1] = { 0 }; | 
|  | volatile int garbage = 0; | 
|  |  | 
|  | rcu_register_thread(); | 
|  |  | 
|  | *(struct rcu_reader_data **)arg = get_ptr_rcu_reader(); | 
|  | while (goflag == GOFLAG_INIT) { | 
|  | g_usleep(1000); | 
|  | } | 
|  | while (goflag == GOFLAG_RUN) { | 
|  | rcu_read_lock(); | 
|  | p = qatomic_rcu_read(&rcu_stress_current); | 
|  | if (qatomic_read(&p->mbtest) == 0) { | 
|  | n_mberror++; | 
|  | } | 
|  | rcu_read_lock(); | 
|  | for (i = 0; i < 100; i++) { | 
|  | garbage++; | 
|  | } | 
|  | rcu_read_unlock(); | 
|  | pc = qatomic_read(&p->age); | 
|  | rcu_read_unlock(); | 
|  | if ((pc > RCU_STRESS_PIPE_LEN) || (pc < 0)) { | 
|  | pc = RCU_STRESS_PIPE_LEN; | 
|  | } | 
|  | rcu_stress_local[pc]++; | 
|  | n_reads_local++; | 
|  | } | 
|  | qemu_mutex_lock(&counts_mutex); | 
|  | n_reads += n_reads_local; | 
|  | for (i = 0; i <= RCU_STRESS_PIPE_LEN; i++) { | 
|  | rcu_stress_count[i] += rcu_stress_local[i]; | 
|  | } | 
|  | qemu_mutex_unlock(&counts_mutex); | 
|  |  | 
|  | rcu_unregister_thread(); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Stress Test Updater | 
|  | * | 
|  | * The updater cycles around updating rcu_stress_current to point at | 
|  | * one of the rcu_stress_array_entries and resets it's age. It | 
|  | * then increments the age of all the other entries. The age | 
|  | * will be read under an rcu_read_lock() and distribution of values | 
|  | * calculated. The final result gives an indication of how many | 
|  | * previously current rcu_stress entries are in flight until the RCU | 
|  | * cycle complete. | 
|  | */ | 
|  | static void *rcu_update_stress_test(void *arg) | 
|  | { | 
|  | int i, rcu_stress_idx = 0; | 
|  | struct rcu_stress *cp = qatomic_read(&rcu_stress_current); | 
|  |  | 
|  | rcu_register_thread(); | 
|  | *(struct rcu_reader_data **)arg = get_ptr_rcu_reader(); | 
|  |  | 
|  | while (goflag == GOFLAG_INIT) { | 
|  | g_usleep(1000); | 
|  | } | 
|  |  | 
|  | while (goflag == GOFLAG_RUN) { | 
|  | struct rcu_stress *p; | 
|  | rcu_stress_idx++; | 
|  | if (rcu_stress_idx >= RCU_STRESS_PIPE_LEN) { | 
|  | rcu_stress_idx = 0; | 
|  | } | 
|  | p = &rcu_stress_array[rcu_stress_idx]; | 
|  | /* catching up with ourselves would be a bug */ | 
|  | assert(p != cp); | 
|  | qatomic_set(&p->mbtest, 0); | 
|  | smp_mb(); | 
|  | qatomic_set(&p->age, 0); | 
|  | qatomic_set(&p->mbtest, 1); | 
|  | qatomic_rcu_set(&rcu_stress_current, p); | 
|  | cp = p; | 
|  | /* | 
|  | * New RCU structure is now live, update pipe counts on old | 
|  | * ones. | 
|  | */ | 
|  | for (i = 0; i < RCU_STRESS_PIPE_LEN; i++) { | 
|  | if (i != rcu_stress_idx) { | 
|  | qatomic_set(&rcu_stress_array[i].age, | 
|  | rcu_stress_array[i].age + 1); | 
|  | } | 
|  | } | 
|  | synchronize_rcu(); | 
|  | n_updates++; | 
|  | } | 
|  |  | 
|  | rcu_unregister_thread(); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static void *rcu_fake_update_stress_test(void *arg) | 
|  | { | 
|  | rcu_register_thread(); | 
|  |  | 
|  | *(struct rcu_reader_data **)arg = get_ptr_rcu_reader(); | 
|  | while (goflag == GOFLAG_INIT) { | 
|  | g_usleep(1000); | 
|  | } | 
|  | while (goflag == GOFLAG_RUN) { | 
|  | synchronize_rcu(); | 
|  | g_usleep(1000); | 
|  | } | 
|  |  | 
|  | rcu_unregister_thread(); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static void stresstest(int nreaders, int duration) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | rcu_stress_current = &rcu_stress_array[0]; | 
|  | rcu_stress_current->age = 0; | 
|  | rcu_stress_current->mbtest = 1; | 
|  | for (i = 0; i < nreaders; i++) { | 
|  | create_thread(rcu_read_stress_test); | 
|  | } | 
|  | create_thread(rcu_update_stress_test); | 
|  | for (i = 0; i < 5; i++) { | 
|  | create_thread(rcu_fake_update_stress_test); | 
|  | } | 
|  | goflag = GOFLAG_RUN; | 
|  | g_usleep(duration * G_USEC_PER_SEC); | 
|  | goflag = GOFLAG_STOP; | 
|  | wait_all_threads(); | 
|  | printf("n_reads: %lld  n_updates: %ld  n_mberror: %d\n", | 
|  | n_reads, n_updates, n_mberror); | 
|  | printf("rcu_stress_count:"); | 
|  | for (i = 0; i <= RCU_STRESS_PIPE_LEN; i++) { | 
|  | printf(" %lld", rcu_stress_count[i]); | 
|  | } | 
|  | printf("\n"); | 
|  | exit(0); | 
|  | } | 
|  |  | 
|  | /* GTest interface */ | 
|  |  | 
|  | static void gtest_stress(int nreaders, int duration) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | rcu_stress_current = &rcu_stress_array[0]; | 
|  | rcu_stress_current->age = 0; | 
|  | rcu_stress_current->mbtest = 1; | 
|  | for (i = 0; i < nreaders; i++) { | 
|  | create_thread(rcu_read_stress_test); | 
|  | } | 
|  | create_thread(rcu_update_stress_test); | 
|  | for (i = 0; i < 5; i++) { | 
|  | create_thread(rcu_fake_update_stress_test); | 
|  | } | 
|  | goflag = GOFLAG_RUN; | 
|  | g_usleep(duration * G_USEC_PER_SEC); | 
|  | goflag = GOFLAG_STOP; | 
|  | wait_all_threads(); | 
|  | g_assert_cmpint(n_mberror, ==, 0); | 
|  | for (i = 2; i <= RCU_STRESS_PIPE_LEN; i++) { | 
|  | g_assert_cmpint(rcu_stress_count[i], ==, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void gtest_stress_1_1(void) | 
|  | { | 
|  | gtest_stress(1, 1); | 
|  | } | 
|  |  | 
|  | static void gtest_stress_10_1(void) | 
|  | { | 
|  | gtest_stress(10, 1); | 
|  | } | 
|  |  | 
|  | static void gtest_stress_1_5(void) | 
|  | { | 
|  | gtest_stress(1, 5); | 
|  | } | 
|  |  | 
|  | static void gtest_stress_10_5(void) | 
|  | { | 
|  | gtest_stress(10, 5); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Mainprogram. | 
|  | */ | 
|  |  | 
|  | static void usage(int argc, char *argv[]) | 
|  | { | 
|  | fprintf(stderr, "Usage: %s [nreaders [ [r|u]perf | stress [duration]]\n", | 
|  | argv[0]); | 
|  | exit(-1); | 
|  | } | 
|  |  | 
|  | int main(int argc, char *argv[]) | 
|  | { | 
|  | int nreaders = 1; | 
|  | int duration = 1; | 
|  |  | 
|  | qemu_mutex_init(&counts_mutex); | 
|  | if (argc >= 2 && argv[1][0] == '-') { | 
|  | g_test_init(&argc, &argv, NULL); | 
|  | if (g_test_quick()) { | 
|  | g_test_add_func("/rcu/torture/1reader", gtest_stress_1_1); | 
|  | g_test_add_func("/rcu/torture/10readers", gtest_stress_10_1); | 
|  | } else { | 
|  | g_test_add_func("/rcu/torture/1reader", gtest_stress_1_5); | 
|  | g_test_add_func("/rcu/torture/10readers", gtest_stress_10_5); | 
|  | } | 
|  | return g_test_run(); | 
|  | } | 
|  |  | 
|  | if (argc >= 2) { | 
|  | nreaders = strtoul(argv[1], NULL, 0); | 
|  | } | 
|  | if (argc > 3) { | 
|  | duration = strtoul(argv[3], NULL, 0); | 
|  | } | 
|  | if (argc < 3 || strcmp(argv[2], "stress") == 0) { | 
|  | stresstest(nreaders, duration); | 
|  | } else if (strcmp(argv[2], "rperf") == 0) { | 
|  | rperftest(nreaders, duration); | 
|  | } else if (strcmp(argv[2], "uperf") == 0) { | 
|  | uperftest(nreaders, duration); | 
|  | } else if (strcmp(argv[2], "perf") == 0) { | 
|  | perftest(nreaders, duration); | 
|  | } | 
|  | usage(argc, argv); | 
|  | return 0; | 
|  | } |