rcu: Add rcutorture system-shutdown capability
Paul E. McKenney [Fri, 4 Nov 2011 18:44:12 +0000 (11:44 -0700)]
Although it is easy to run rcutorture tests under KVM, there is currently
no nice way to run such a test for a fixed time period, collect all of
the rcutorture data, and then shut the system down cleanly.  This commit
therefore adds an rcutorture module parameter named "shutdown_secs" that
specified the run duration in seconds, after which rcutorture terminates
the test and powers the system down.  The default value for "shutdown_secs"
is zero, which disables shutdown.

Signed-off-by: Paul E. McKenney <paul.mckenney@linaro.org>
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>

Documentation/RCU/torture.txt
kernel/rcutorture.c

index 783d6c1..af40929 100644 (file)
@@ -66,6 +66,11 @@ shuffle_interval
                to a particular subset of the CPUs, defaults to 3 seconds.
                Used in conjunction with test_no_idle_hz.
 
+shutdown_secs  The number of seconds to run the test before terminating
+               the test and powering off the system.  The default is
+               zero, which disables test termination and system shutdown.
+               This capability is useful for automated testing.
+
 stat_interval  The number of seconds between output of torture
                statistics (via printk()).  Regardless of the interval,
                statistics are printed when the module is unloaded.
index df35228..ce84091 100644 (file)
@@ -61,9 +61,10 @@ static int test_no_idle_hz;  /* Test RCU's support for tickless idle CPUs. */
 static int shuffle_interval = 3; /* Interval between shuffles (in sec)*/
 static int stutter = 5;                /* Start/stop testing interval (in sec) */
 static int irqreader = 1;      /* RCU readers from irq (timers). */
-static int fqs_duration = 0;   /* Duration of bursts (us), 0 to disable. */
-static int fqs_holdoff = 0;    /* Hold time within burst (us). */
+static int fqs_duration;       /* Duration of bursts (us), 0 to disable. */
+static int fqs_holdoff;                /* Hold time within burst (us). */
 static int fqs_stutter = 3;    /* Wait time between bursts (s). */
+static int shutdown_secs;      /* Shutdown time (s).  <=0 for no shutdown. */
 static int test_boost = 1;     /* Test RCU prio boost: 0=no, 1=maybe, 2=yes. */
 static int test_boost_interval = 7; /* Interval between boost tests, seconds. */
 static int test_boost_duration = 4; /* Duration of each boost test, seconds. */
@@ -91,6 +92,8 @@ module_param(fqs_holdoff, int, 0444);
 MODULE_PARM_DESC(fqs_holdoff, "Holdoff time within fqs bursts (us)");
 module_param(fqs_stutter, int, 0444);
 MODULE_PARM_DESC(fqs_stutter, "Wait time between fqs bursts (s)");
+module_param(shutdown_secs, int, 0444);
+MODULE_PARM_DESC(shutdown_secs, "Shutdown time (s), zero to disable.");
 module_param(test_boost, int, 0444);
 MODULE_PARM_DESC(test_boost, "Test RCU prio boost: 0=no, 1=maybe, 2=yes.");
 module_param(test_boost_interval, int, 0444);
@@ -119,6 +122,7 @@ static struct task_struct *shuffler_task;
 static struct task_struct *stutter_task;
 static struct task_struct *fqs_task;
 static struct task_struct *boost_tasks[NR_CPUS];
+static struct task_struct *shutdown_task;
 
 #define RCU_TORTURE_PIPE_LEN 10
 
@@ -167,6 +171,7 @@ int rcutorture_runnable = RCUTORTURE_RUNNABLE_INIT;
 #define rcu_can_boost() 0
 #endif /* #else #if defined(CONFIG_RCU_BOOST) && !defined(CONFIG_HOTPLUG_CPU) */
 
+static unsigned long shutdown_time;    /* jiffies to system shutdown. */
 static unsigned long boost_starttime;  /* jiffies of next boost test start. */
 DEFINE_MUTEX(boost_mutex);             /* protect setting boost_starttime */
                                        /*  and boost task create/destroy. */
@@ -182,6 +187,9 @@ static int fullstop = FULLSTOP_RMMOD;
  */
 static DEFINE_MUTEX(fullstop_mutex);
 
+/* Forward reference. */
+static void rcu_torture_cleanup(void);
+
 /*
  * Detect and respond to a system shutdown.
  */
@@ -1250,12 +1258,12 @@ rcu_torture_print_module_parms(struct rcu_torture_ops *cur_ops, char *tag)
                "shuffle_interval=%d stutter=%d irqreader=%d "
                "fqs_duration=%d fqs_holdoff=%d fqs_stutter=%d "
                "test_boost=%d/%d test_boost_interval=%d "
-               "test_boost_duration=%d\n",
+               "test_boost_duration=%d shutdown_secs=%d\n",
                torture_type, tag, nrealreaders, nfakewriters,
                stat_interval, verbose, test_no_idle_hz, shuffle_interval,
                stutter, irqreader, fqs_duration, fqs_holdoff, fqs_stutter,
                test_boost, cur_ops->can_boost,
-               test_boost_interval, test_boost_duration);
+               test_boost_interval, test_boost_duration, shutdown_secs);
 }
 
 static struct notifier_block rcutorture_shutdown_nb = {
@@ -1305,6 +1313,43 @@ static int rcutorture_booster_init(int cpu)
        return 0;
 }
 
+/*
+ * Cause the rcutorture test to shutdown the system after the test has
+ * run for the time specified by the shutdown_secs module parameter.
+ */
+static int
+rcu_torture_shutdown(void *arg)
+{
+       long delta;
+       unsigned long jiffies_snap;
+
+       VERBOSE_PRINTK_STRING("rcu_torture_shutdown task started");
+       jiffies_snap = ACCESS_ONCE(jiffies);
+       while (ULONG_CMP_LT(jiffies_snap, shutdown_time) &&
+              !kthread_should_stop()) {
+               delta = shutdown_time - jiffies_snap;
+               if (verbose)
+                       printk(KERN_ALERT "%s" TORTURE_FLAG
+                              "rcu_torture_shutdown task: %lu "
+                              "jiffies remaining\n",
+                              torture_type, delta);
+               schedule_timeout_interruptible(delta);
+               jiffies_snap = ACCESS_ONCE(jiffies);
+       }
+       if (ULONG_CMP_LT(jiffies, shutdown_time)) {
+               VERBOSE_PRINTK_STRING("rcu_torture_shutdown task stopping");
+               return 0;
+       }
+
+       /* OK, shut down the system. */
+
+       VERBOSE_PRINTK_STRING("rcu_torture_shutdown task shutting down system");
+       shutdown_task = NULL;   /* Avoid self-kill deadlock. */
+       rcu_torture_cleanup();  /* Get the success/failure message. */
+       kernel_power_off();     /* Shut down the system. */
+       return 0;
+}
+
 static int rcutorture_cpu_notify(struct notifier_block *self,
                                 unsigned long action, void *hcpu)
 {
@@ -1409,6 +1454,10 @@ rcu_torture_cleanup(void)
                for_each_possible_cpu(i)
                        rcutorture_booster_cleanup(i);
        }
+       if (shutdown_task != NULL) {
+               VERBOSE_PRINTK_STRING("Stopping rcu_torture_shutdown task");
+               kthread_stop(shutdown_task);
+       }
 
        /* Wait for all RCU callbacks to fire.  */
 
@@ -1625,6 +1674,17 @@ rcu_torture_init(void)
                        }
                }
        }
+       if (shutdown_secs > 0) {
+               shutdown_time = jiffies + shutdown_secs * HZ;
+               shutdown_task = kthread_run(rcu_torture_shutdown, NULL,
+                                           "rcu_torture_shutdown");
+               if (IS_ERR(shutdown_task)) {
+                       firsterr = PTR_ERR(shutdown_task);
+                       VERBOSE_PRINTK_ERRSTRING("Failed to create shutdown");
+                       shutdown_task = NULL;
+                       goto unwind;
+               }
+       }
        register_reboot_notifier(&rcutorture_shutdown_nb);
        rcutorture_record_test_transition();
        mutex_unlock(&fullstop_mutex);