torture: Add refperf to the rcutorture scripting
[linux-block.git] / kernel / rcu / refperf.c
CommitLineData
653ed64b
JFG
1// SPDX-License-Identifier: GPL-2.0+
2//
3// Performance test comparing RCU vs other mechanisms
4// for acquiring references on objects.
5//
6// Copyright (C) Google, 2020.
7//
8// Author: Joel Fernandes <joel@joelfernandes.org>
9
10#define pr_fmt(fmt) fmt
11
12#include <linux/atomic.h>
13#include <linux/bitops.h>
14#include <linux/completion.h>
15#include <linux/cpu.h>
16#include <linux/delay.h>
17#include <linux/err.h>
18#include <linux/init.h>
19#include <linux/interrupt.h>
20#include <linux/kthread.h>
21#include <linux/kernel.h>
22#include <linux/mm.h>
23#include <linux/module.h>
24#include <linux/moduleparam.h>
25#include <linux/notifier.h>
26#include <linux/percpu.h>
27#include <linux/rcupdate.h>
28#include <linux/reboot.h>
29#include <linux/sched.h>
30#include <linux/spinlock.h>
31#include <linux/smp.h>
32#include <linux/stat.h>
33#include <linux/srcu.h>
34#include <linux/slab.h>
35#include <linux/torture.h>
36#include <linux/types.h>
37
38#include "rcu.h"
39
40#define PERF_FLAG "-ref-perf: "
41
42#define PERFOUT(s, x...) \
43 pr_alert("%s" PERF_FLAG s, perf_type, ## x)
44
45#define VERBOSE_PERFOUT(s, x...) \
46 do { if (verbose) pr_alert("%s" PERF_FLAG s, perf_type, ## x); } while (0)
47
48#define VERBOSE_PERFOUT_ERRSTRING(s, x...) \
49 do { if (verbose) pr_alert("%s" PERF_FLAG "!!! " s, perf_type, ## x); } while (0)
50
51MODULE_LICENSE("GPL");
52MODULE_AUTHOR("Joel Fernandes (Google) <joel@joelfernandes.org>");
53
54static char *perf_type = "rcu";
55module_param(perf_type, charp, 0444);
56MODULE_PARM_DESC(perf_type, "Type of test (rcu, srcu, refcnt, rwsem, rwlock.");
57
58torture_param(int, verbose, 0, "Enable verbose debugging printk()s");
59
60// Number of loops per experiment, all readers execute an operation concurrently
61torture_param(long, loops, 10000000, "Number of loops per experiment.");
62
63#ifdef MODULE
64# define REFPERF_SHUTDOWN 0
65#else
66# define REFPERF_SHUTDOWN 1
67#endif
68
69torture_param(bool, shutdown, REFPERF_SHUTDOWN,
70 "Shutdown at end of performance tests.");
71
72struct reader_task {
73 struct task_struct *task;
74 atomic_t start;
75 wait_queue_head_t wq;
76 u64 last_duration_ns;
77
78 // The average latency When 1..<this reader> are concurrently
79 // running an experiment. For example, if this reader_task is
80 // of index 5 in the reader_tasks array, then result is for
81 // 6 cores.
82 u64 result_avg;
83};
84
85static struct task_struct *shutdown_task;
86static wait_queue_head_t shutdown_wq;
87
88static struct task_struct *main_task;
89static wait_queue_head_t main_wq;
90static int shutdown_start;
91
92static struct reader_task *reader_tasks;
93static int nreaders;
94
95// Number of readers that are part of the current experiment.
96static atomic_t nreaders_exp;
97
98// Use to wait for all threads to start.
99static atomic_t n_init;
100
101// Track which experiment is currently running.
102static int exp_idx;
103
104// Operations vector for selecting different types of tests.
105struct ref_perf_ops {
106 void (*init)(void);
107 void (*cleanup)(void);
108 int (*readlock)(void);
109 void (*readunlock)(int idx);
110 const char *name;
111};
112
113static struct ref_perf_ops *cur_ops;
114
115// Definitions for RCU ref perf testing.
116static int ref_rcu_read_lock(void) __acquires(RCU)
117{
118 rcu_read_lock();
119 return 0;
120}
121
122static void ref_rcu_read_unlock(int idx) __releases(RCU)
123{
124 rcu_read_unlock();
125}
126
127static void rcu_sync_perf_init(void)
128{
129}
130
131static struct ref_perf_ops rcu_ops = {
132 .init = rcu_sync_perf_init,
133 .readlock = ref_rcu_read_lock,
134 .readunlock = ref_rcu_read_unlock,
135 .name = "rcu"
136};
137
138
139// Definitions for SRCU ref perf testing.
140DEFINE_STATIC_SRCU(srcu_refctl_perf);
141static struct srcu_struct *srcu_ctlp = &srcu_refctl_perf;
142
143static int srcu_ref_perf_read_lock(void) __acquires(srcu_ctlp)
144{
145 return srcu_read_lock(srcu_ctlp);
146}
147
148static void srcu_ref_perf_read_unlock(int idx) __releases(srcu_ctlp)
149{
150 srcu_read_unlock(srcu_ctlp, idx);
151}
152
153static struct ref_perf_ops srcu_ops = {
154 .init = rcu_sync_perf_init,
155 .readlock = srcu_ref_perf_read_lock,
156 .readunlock = srcu_ref_perf_read_unlock,
157 .name = "srcu"
158};
159
160// Definitions for reference count
161static atomic_t refcnt;
162
163static int srcu_ref_perf_refcnt_lock(void)
164{
165 atomic_inc(&refcnt);
166 return 0;
167}
168
169static void srcu_ref_perf_refcnt_unlock(int idx) __releases(srcu_ctlp)
170{
171 atomic_dec(&refcnt);
172 srcu_read_unlock(srcu_ctlp, idx);
173}
174
175static struct ref_perf_ops refcnt_ops = {
176 .init = rcu_sync_perf_init,
177 .readlock = srcu_ref_perf_refcnt_lock,
178 .readunlock = srcu_ref_perf_refcnt_unlock,
179 .name = "refcnt"
180};
181
182// Definitions for rwlock
183static rwlock_t test_rwlock;
184
185static void ref_perf_rwlock_init(void)
186{
187 rwlock_init(&test_rwlock);
188}
189
190static int ref_perf_rwlock_lock(void)
191{
192 read_lock(&test_rwlock);
193 return 0;
194}
195
196static void ref_perf_rwlock_unlock(int idx)
197{
198 read_unlock(&test_rwlock);
199}
200
201static struct ref_perf_ops rwlock_ops = {
202 .init = ref_perf_rwlock_init,
203 .readlock = ref_perf_rwlock_lock,
204 .readunlock = ref_perf_rwlock_unlock,
205 .name = "rwlock"
206};
207
208// Definitions for rwsem
209static struct rw_semaphore test_rwsem;
210
211static void ref_perf_rwsem_init(void)
212{
213 init_rwsem(&test_rwsem);
214}
215
216static int ref_perf_rwsem_lock(void)
217{
218 down_read(&test_rwsem);
219 return 0;
220}
221
222static void ref_perf_rwsem_unlock(int idx)
223{
224 up_read(&test_rwsem);
225}
226
227static struct ref_perf_ops rwsem_ops = {
228 .init = ref_perf_rwsem_init,
229 .readlock = ref_perf_rwsem_lock,
230 .readunlock = ref_perf_rwsem_unlock,
231 .name = "rwsem"
232};
233
234// Reader kthread. Repeatedly does empty RCU read-side
235// critical section, minimizing update-side interference.
236static int
237ref_perf_reader(void *arg)
238{
239 unsigned long flags;
240 long me = (long)arg;
241 struct reader_task *rt = &(reader_tasks[me]);
242 unsigned long spincnt;
243 int idx;
244 u64 start;
245 s64 duration;
246
247 VERBOSE_PERFOUT("ref_perf_reader %ld: task started", me);
248 set_cpus_allowed_ptr(current, cpumask_of(me % nr_cpu_ids));
249 set_user_nice(current, MAX_NICE);
250 atomic_inc(&n_init);
251repeat:
252 VERBOSE_PERFOUT("ref_perf_reader %ld: waiting to start next experiment on cpu %d", me, smp_processor_id());
253
254 // Wait for signal that this reader can start.
255 wait_event(rt->wq, (atomic_read(&nreaders_exp) && atomic_read(&rt->start)) ||
256 torture_must_stop());
257
258 if (torture_must_stop())
259 goto end;
260
261 // Make sure that the CPU is affinitized appropriately during testing.
262 WARN_ON_ONCE(smp_processor_id() != me);
263
264 atomic_dec(&rt->start);
265
266 // To prevent noise, keep interrupts disabled. This also has the
267 // effect of preventing entries into slow path for rcu_read_unlock().
268 local_irq_save(flags);
269 start = ktime_get_mono_fast_ns();
270
271 VERBOSE_PERFOUT("ref_perf_reader %ld: experiment %d started", me, exp_idx);
272
273 for (spincnt = 0; spincnt < loops; spincnt++) {
274 idx = cur_ops->readlock();
275 cur_ops->readunlock(idx);
276 }
277
278 duration = ktime_get_mono_fast_ns() - start;
279 local_irq_restore(flags);
280
281 rt->last_duration_ns = WARN_ON_ONCE(duration < 0) ? 0 : duration;
282
283 atomic_dec(&nreaders_exp);
284
285 VERBOSE_PERFOUT("ref_perf_reader %ld: experiment %d ended, (readers remaining=%d)",
286 me, exp_idx, atomic_read(&nreaders_exp));
287
288 if (!atomic_read(&nreaders_exp))
289 wake_up(&main_wq);
290
291 if (!torture_must_stop())
292 goto repeat;
293end:
294 torture_kthread_stopping("ref_perf_reader");
295 return 0;
296}
297
298void reset_readers(int n)
299{
300 int i;
301 struct reader_task *rt;
302
303 for (i = 0; i < n; i++) {
304 rt = &(reader_tasks[i]);
305
306 rt->last_duration_ns = 0;
307 }
308}
309
310// Print the results of each reader and return the sum of all their durations.
311u64 process_durations(int n)
312{
313 int i;
314 struct reader_task *rt;
315 char buf1[64];
316 char buf[512];
317 u64 sum = 0;
318
319 buf[0] = 0;
320 sprintf(buf, "Experiment #%d (Format: <THREAD-NUM>:<Total loop time in ns>)",
321 exp_idx);
322
323 for (i = 0; i <= n && !torture_must_stop(); i++) {
324 rt = &(reader_tasks[i]);
325 sprintf(buf1, "%d: %llu\t", i, rt->last_duration_ns);
326
327 if (i % 5 == 0)
328 strcat(buf, "\n");
329 strcat(buf, buf1);
330
331 sum += rt->last_duration_ns;
332 }
333 strcat(buf, "\n");
334
335 PERFOUT("%s\n", buf);
336
337 return sum;
338}
339
340// The main_func is the main orchestrator, it performs a bunch of
341// experiments. For every experiment, it orders all the readers
342// involved to start and waits for them to finish the experiment. It
343// then reads their timestamps and starts the next experiment. Each
344// experiment progresses from 1 concurrent reader to N of them at which
345// point all the timestamps are printed.
346static int main_func(void *arg)
347{
348 int exp, r;
349 char buf1[64];
350 char buf[512];
351
352 set_cpus_allowed_ptr(current, cpumask_of(nreaders % nr_cpu_ids));
353 set_user_nice(current, MAX_NICE);
354
355 VERBOSE_PERFOUT("main_func task started");
356 atomic_inc(&n_init);
357
358 // Wait for all threads to start.
359 wait_event(main_wq, atomic_read(&n_init) == (nreaders + 1));
360
361 // Start exp readers up per experiment
362 for (exp = 0; exp < nreaders && !torture_must_stop(); exp++) {
363 if (torture_must_stop())
364 goto end;
365
366 reset_readers(exp);
367 atomic_set(&nreaders_exp, exp + 1);
368
369 exp_idx = exp;
370
371 for (r = 0; r <= exp; r++) {
372 atomic_set(&reader_tasks[r].start, 1);
373 wake_up(&reader_tasks[r].wq);
374 }
375
376 VERBOSE_PERFOUT("main_func: experiment started, waiting for %d readers",
377 exp);
378
379 wait_event(main_wq,
380 !atomic_read(&nreaders_exp) || torture_must_stop());
381
382 VERBOSE_PERFOUT("main_func: experiment ended");
383
384 if (torture_must_stop())
385 goto end;
386
387 reader_tasks[exp].result_avg = process_durations(exp) / ((exp + 1) * loops);
388 }
389
390 // Print the average of all experiments
391 PERFOUT("END OF TEST. Calculating average duration per loop (nanoseconds)...\n");
392
393 buf[0] = 0;
394 strcat(buf, "\n");
395 strcat(buf, "Threads\tTime(ns)\n");
396
397 for (exp = 0; exp < nreaders; exp++) {
398 sprintf(buf1, "%d\t%llu\n", exp + 1, reader_tasks[exp].result_avg);
399 strcat(buf, buf1);
400 }
401
402 PERFOUT("%s", buf);
403
404 // This will shutdown everything including us.
405 if (shutdown) {
406 shutdown_start = 1;
407 wake_up(&shutdown_wq);
408 }
409
410 // Wait for torture to stop us
411 while (!torture_must_stop())
412 schedule_timeout_uninterruptible(1);
413
414end:
415 torture_kthread_stopping("main_func");
416 return 0;
417}
418
419static void
420ref_perf_print_module_parms(struct ref_perf_ops *cur_ops, const char *tag)
421{
422 pr_alert("%s" PERF_FLAG
423 "--- %s: verbose=%d shutdown=%d loops=%ld\n", perf_type, tag,
424 verbose, shutdown, loops);
425}
426
427static void
428ref_perf_cleanup(void)
429{
430 int i;
431
432 if (torture_cleanup_begin())
433 return;
434
435 if (!cur_ops) {
436 torture_cleanup_end();
437 return;
438 }
439
440 if (reader_tasks) {
441 for (i = 0; i < nreaders; i++)
442 torture_stop_kthread("ref_perf_reader",
443 reader_tasks[i].task);
444 }
445 kfree(reader_tasks);
446
447 torture_stop_kthread("main_task", main_task);
448 kfree(main_task);
449
450 // Do perf-type-specific cleanup operations.
451 if (cur_ops->cleanup != NULL)
452 cur_ops->cleanup();
453
454 torture_cleanup_end();
455}
456
457// Shutdown kthread. Just waits to be awakened, then shuts down system.
458static int
459ref_perf_shutdown(void *arg)
460{
461 wait_event(shutdown_wq, shutdown_start);
462
463 smp_mb(); // Wake before output.
464 ref_perf_cleanup();
465 kernel_power_off();
466
467 return -EINVAL;
468}
469
470static int __init
471ref_perf_init(void)
472{
473 long i;
474 int firsterr = 0;
475 static struct ref_perf_ops *perf_ops[] = {
476 &rcu_ops, &srcu_ops, &refcnt_ops, &rwlock_ops, &rwsem_ops,
477 };
478
479 if (!torture_init_begin(perf_type, verbose))
480 return -EBUSY;
481
482 for (i = 0; i < ARRAY_SIZE(perf_ops); i++) {
483 cur_ops = perf_ops[i];
484 if (strcmp(perf_type, cur_ops->name) == 0)
485 break;
486 }
487 if (i == ARRAY_SIZE(perf_ops)) {
488 pr_alert("rcu-perf: invalid perf type: \"%s\"\n", perf_type);
489 pr_alert("rcu-perf types:");
490 for (i = 0; i < ARRAY_SIZE(perf_ops); i++)
491 pr_cont(" %s", perf_ops[i]->name);
492 pr_cont("\n");
493 WARN_ON(!IS_MODULE(CONFIG_RCU_REF_PERF_TEST));
494 firsterr = -EINVAL;
495 cur_ops = NULL;
496 goto unwind;
497 }
498 if (cur_ops->init)
499 cur_ops->init();
500
501 ref_perf_print_module_parms(cur_ops, "Start of test");
502
503 // Shutdown task
504 if (shutdown) {
505 init_waitqueue_head(&shutdown_wq);
506 firsterr = torture_create_kthread(ref_perf_shutdown, NULL,
507 shutdown_task);
508 if (firsterr)
509 goto unwind;
510 schedule_timeout_uninterruptible(1);
511 }
512
513 // Reader tasks (~75% of online CPUs).
514 nreaders = (num_online_cpus() >> 1) + (num_online_cpus() >> 2);
515 reader_tasks = kcalloc(nreaders, sizeof(reader_tasks[0]),
516 GFP_KERNEL);
517 if (!reader_tasks) {
518 VERBOSE_PERFOUT_ERRSTRING("out of memory");
519 firsterr = -ENOMEM;
520 goto unwind;
521 }
522
523 VERBOSE_PERFOUT("Starting %d reader threads\n", nreaders);
524
525 for (i = 0; i < nreaders; i++) {
526 firsterr = torture_create_kthread(ref_perf_reader, (void *)i,
527 reader_tasks[i].task);
528 if (firsterr)
529 goto unwind;
530
531 init_waitqueue_head(&(reader_tasks[i].wq));
532 }
533
534 // Main Task
535 init_waitqueue_head(&main_wq);
536 firsterr = torture_create_kthread(main_func, NULL, main_task);
537 if (firsterr)
538 goto unwind;
539 schedule_timeout_uninterruptible(1);
540
541
542 // Wait until all threads start
543 while (atomic_read(&n_init) < nreaders + 1)
544 schedule_timeout_uninterruptible(1);
545
546 wake_up(&main_wq);
547
548 torture_init_end();
549 return 0;
550
551unwind:
552 torture_init_end();
553 ref_perf_cleanup();
554 return firsterr;
555}
556
557module_init(ref_perf_init);
558module_exit(ref_perf_cleanup);