changeset 1006:2e7682a5da28

7901022: JMH runners should provide explicit timeout setting
author shade
date Thu, 11 Sep 2014 13:40:39 +0400
parents 76d7ea927d5d
children 9b4287ecb19c
files jmh-core/src/main/java/org/openjdk/jmh/infra/BenchmarkParams.java jmh-core/src/main/java/org/openjdk/jmh/runner/Defaults.java jmh-core/src/main/java/org/openjdk/jmh/runner/LoopBenchmarkHandler.java jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java jmh-core/src/main/java/org/openjdk/jmh/runner/format/TextReportFormat.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/ChainedOptionsBuilder.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/CommandLineOptions.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/Options.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/OptionsBuilder.java jmh-core/src/test/java/org/openjdk/jmh/results/TestAggregateResult.java jmh-core/src/test/java/org/openjdk/jmh/results/format/ResultFormatTest.java jmh-core/src/test/java/org/openjdk/jmh/runner/RunnerTest.java jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestOptions.java jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestParentOptions.java jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_30_Interrupts.java
diffstat 15 files changed, 161 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/jmh-core/src/main/java/org/openjdk/jmh/infra/BenchmarkParams.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/infra/BenchmarkParams.java	Thu Sep 11 13:40:39 2014 +0400
@@ -28,6 +28,7 @@
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.runner.WorkloadParams;
+import org.openjdk.jmh.runner.options.TimeValue;
 import org.openjdk.jmh.util.Utils;
 
 import java.io.Serializable;
@@ -74,13 +75,14 @@
                              IterationParams warmup, IterationParams measurement,
                              Mode mode, WorkloadParams params,
                              TimeUnit timeUnit, int opsPerInvocation,
-                             String jvm, Collection<String> jvmArgs) {
+                             String jvm, Collection<String> jvmArgs,
+                             TimeValue timeout) {
         super(benchmark, generatedTarget, synchIterations,
                 threads, threadGroups, forks, warmupForks,
                 warmup, measurement,
                 mode, params,
                 timeUnit, opsPerInvocation,
-                jvm, jvmArgs);
+                jvm, jvmArgs, timeout);
     }
 }
 
@@ -93,13 +95,15 @@
                              IterationParams warmup, IterationParams measurement,
                              Mode mode, WorkloadParams params,
                              TimeUnit timeUnit, int opsPerInvocation,
-                             String jvm, Collection<String> jvmArgs) {
+                             String jvm, Collection<String> jvmArgs,
+                             TimeValue timeout) {
         super(benchmark, generatedTarget, synchIterations,
                 threads, threadGroups, forks, warmupForks,
                 warmup, measurement,
                 mode, params,
                 timeUnit, opsPerInvocation,
-                jvm, jvmArgs);
+                jvm, jvmArgs,
+                timeout);
     }
 
     public BenchmarkParamsL4(BenchmarkParams other) {
@@ -132,13 +136,15 @@
                              IterationParams warmup, IterationParams measurement,
                              Mode mode, WorkloadParams params,
                              TimeUnit timeUnit, int opsPerInvocation,
-                             String jvm, Collection<String> jvmArgs) {
+                             String jvm, Collection<String> jvmArgs,
+                             TimeValue timeout) {
         super(benchmark, generatedTarget, synchIterations,
                 threads, threadGroups, forks, warmupForks,
                 warmup, measurement,
                 mode, params,
                 timeUnit, opsPerInvocation,
-                jvm, jvmArgs);
+                jvm, jvmArgs,
+                timeout);
     }
 
     public BenchmarkParamsL3(BenchmarkParams other) {
@@ -187,13 +193,15 @@
     protected final int opsPerInvocation;
     protected final String jvm;
     protected final Collection<String> jvmArgs;
+    protected final TimeValue timeout;
 
     public BenchmarkParamsL2(String benchmark, String generatedTarget, boolean synchIterations,
                              int threads, int[] threadGroups, int forks, int warmupForks,
                              IterationParams warmup, IterationParams measurement,
                              Mode mode, WorkloadParams params,
                              TimeUnit timeUnit, int opsPerInvocation,
-                             String jvm, Collection<String> jvmArgs) {
+                             String jvm, Collection<String> jvmArgs,
+                             TimeValue timeout) {
         this.benchmark = benchmark;
         this.generatedTarget = generatedTarget;
         this.synchIterations = synchIterations;
@@ -209,6 +217,7 @@
         this.opsPerInvocation = opsPerInvocation;
         this.jvm = jvm;
         this.jvmArgs = jvmArgs;
+        this.timeout = timeout;
     }
 
     public BenchmarkParamsL2(BenchmarkParams other) {
@@ -227,6 +236,14 @@
         this.opsPerInvocation = other.opsPerInvocation;
         this.jvm = other.jvm;
         this.jvmArgs = other.jvmArgs;
+        this.timeout = other.timeout;
+    }
+
+    /**
+     * @return how long to wait for iteration to complete
+     */
+    public TimeValue getTimeout() {
+        return timeout;
     }
 
     /**
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/Defaults.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/Defaults.java	Thu Sep 11 13:40:39 2014 +0400
@@ -141,4 +141,9 @@
      * Default operations per invocation.
      */
     public static final Integer OPS_PER_INVOCATION = 1;
+
+    /**
+     * Default timeout.
+     */
+    public static final TimeValue TIMEOUT = TimeValue.minutes(10);
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/LoopBenchmarkHandler.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/LoopBenchmarkHandler.java	Thu Sep 11 13:40:39 2014 +0400
@@ -81,6 +81,8 @@
             runners[i] = new BenchmarkTask(control, threadParamses[i]);
         }
 
+        long waitDeadline = System.nanoTime() + benchmarkParams.getTimeout().convertTo(TimeUnit.NANOSECONDS);
+
         // profilers start way before the workload starts to capture
         // the edge behaviors.
         startProfilers(benchmarkParams, params);
@@ -125,21 +127,6 @@
             }
         }
 
-        // Adjust waiting intervals:
-        //  - We don't know the running time for SingleShot benchmarks,
-        //    we wait for at least 10 minutes for benchmark to stop; this
-        //    can be adjusted with usual warmup/measurement duration settings;
-        //  - For other benchmarks, we wait for twice the run time,
-        //    but at least 5 seconds to cover for low run times.
-        long timeToWait;
-        switch (benchmarkParams.getMode()) {
-            case SingleShotTime:
-                timeToWait = Math.max(TimeUnit.SECONDS.toNanos(600), runtime.convertTo(TimeUnit.NANOSECONDS));
-                break;
-            default:
-                timeToWait = Math.max(runtime.convertTo(TimeUnit.NANOSECONDS) * 2, TimeUnit.SECONDS.toNanos(5));
-        }
-
         // Wait for the result, continuously polling the worker threads.
         // The abrupt exception in any worker will float up here.
         int expected = numThreads;
@@ -147,7 +134,8 @@
             for (BenchmarkTask task : results.keySet()) {
                 Future<Collection<? extends Result>> fr = results.get(task);
                 try {
-                    fr.get(timeToWait, TimeUnit.NANOSECONDS);
+                    long waitFor = Math.max(TimeUnit.MILLISECONDS.toNanos(100), waitDeadline - System.nanoTime());
+                    fr.get(waitFor, TimeUnit.NANOSECONDS);
                     expected--;
                 } catch (InterruptedException ex) {
                     throw new BenchmarkException(ex);
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java	Thu Sep 11 13:40:39 2014 +0400
@@ -457,10 +457,13 @@
         jvmArgs.addAll(options.getJvmArgsAppend().orElse(
                 benchmark.getJvmArgsAppend().orElse(Collections.<String>emptyList())));
 
+        TimeValue timeout = options.getTimeout().orElse(
+                Defaults.TIMEOUT);
+
         return new BenchmarkParams(benchmark.getUsername(), benchmark.generatedTarget(), synchIterations,
                 threads, threadGroups, forks, warmupForks,
                 warmup, measurement, benchmark.getMode(), benchmark.getWorkloadParams(), timeUnit, opsPerInvocation,
-                jvm, jvmArgs);
+                jvm, jvmArgs, timeout);
     }
 
     private List<WorkloadParams> explodeAllParams(BenchmarkListEntry br) throws RunnerException {
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/format/TextReportFormat.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/format/TextReportFormat.java	Thu Sep 11 13:40:39 2014 +0400
@@ -34,12 +34,14 @@
 import org.openjdk.jmh.results.format.ResultFormatFactory;
 import org.openjdk.jmh.results.format.ResultFormatType;
 import org.openjdk.jmh.runner.IterationType;
+import org.openjdk.jmh.runner.options.TimeValue;
 import org.openjdk.jmh.runner.options.VerboseMode;
 
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 /**
  * TextReportFormat implementation of OutputFormat.
@@ -52,26 +54,34 @@
 
     @Override
     public void startBenchmark(BenchmarkParams params) {
-        if (params.getWarmup().getCount() > 0) {
-            out.println("# Warmup: " + params.getWarmup().getCount() + " iterations, " +
-                    params.getWarmup().getTime() + " each" +
-                    (params.getWarmup().getBatchSize() <= 1 ? "" : ", " + params.getWarmup().getBatchSize() + " calls per op"));
+        IterationParams warmup = params.getWarmup();
+        if (warmup.getCount() > 0) {
+            out.println("# Warmup: " + warmup.getCount() + " iterations, " +
+                    warmup.getTime() + " each" +
+                    (warmup.getBatchSize() <= 1 ? "" : ", " + warmup.getBatchSize() + " calls per op"));
         } else {
             out.println("# Warmup: <none>");
         }
 
-        if (params.getMeasurement().getCount() > 0) {
-            out.println("# Measurement: " + params.getMeasurement().getCount() + " iterations, " +
-                    params.getMeasurement().getTime() + " each" +
-                    (params.getMeasurement().getBatchSize() <= 1 ? "" : ", " + params.getMeasurement().getBatchSize() + " calls per op"));
+        IterationParams measurement = params.getMeasurement();
+        if (measurement.getCount() > 0) {
+            out.println("# Measurement: " + measurement.getCount() + " iterations, " +
+                    measurement.getTime() + " each" +
+                    (measurement.getBatchSize() <= 1 ? "" : ", " + measurement.getBatchSize() + " calls per op"));
         } else {
             out.println("# Measurement: <none>");
         }
 
+        TimeValue timeout = params.getTimeout();
+        boolean timeoutWarning = (timeout.convertTo(TimeUnit.NANOSECONDS) <= measurement.getTime().convertTo(TimeUnit.NANOSECONDS)) ||
+                (timeout.convertTo(TimeUnit.NANOSECONDS) <= warmup.getTime().convertTo(TimeUnit.NANOSECONDS));
+        out.println("# Timeout: " + timeout + " per iteration" + (timeoutWarning ? ", ***WARNING: The timeout might be too low!***" : ""));
+
         out.println("# Threads: " + params.getThreads() + " " + getThreadsString(params.getThreads()) +
                 (params.shouldSynchIterations() ?
                         ", will synchronize iterations" :
                         (params.getMode() == Mode.SingleShotTime) ? "" : ", ***WARNING: Synchronize iterations are disabled!***"));
+
         out.println("# Benchmark mode: " + params.getMode().longLabel());
         out.println("# Benchmark: " + params.getBenchmark());
         if (!params.getParamsKeys().isEmpty()) {
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/ChainedOptionsBuilder.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/ChainedOptionsBuilder.java	Thu Sep 11 13:40:39 2014 +0400
@@ -298,4 +298,11 @@
      */
     ChainedOptionsBuilder param(String name, String... values);
 
+    /**
+     * How long to wait for iteration execution?
+     * @param value time
+     * @return builder
+     */
+    ChainedOptionsBuilder timeout(TimeValue value);
+
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/CommandLineOptions.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/CommandLineOptions.java	Thu Sep 11 13:40:39 2014 +0400
@@ -53,6 +53,7 @@
     private static final long serialVersionUID = 5565183446360224399L;
 
     private final Optional<Integer> iterations;
+    private final Optional<TimeValue> timeout;
     private final Optional<TimeValue> runTime;
     private final Optional<Integer> batchSize;
     private final Optional<Integer> warmupIterations;
@@ -118,6 +119,9 @@
         OptionSpec<String> optWarmupTime = parser.accepts("w", "Time to spend at each warmup iteration.")
                 .withRequiredArg().ofType(String.class).describedAs("time");
 
+        OptionSpec<String> optTimeoutTime = parser.accepts("to", "Timeout for benchmark iteration.")
+                .withRequiredArg().ofType(String.class).describedAs("time");
+
         OptionSpec<String> optThreads = parser.accepts("t", "Number of worker threads to run with.")
                 .withRequiredArg().ofType(String.class).describedAs("int");
 
@@ -296,6 +300,17 @@
                 warmupTime = Optional.none();
             }
 
+            if (set.has(optTimeoutTime)) {
+                String value = optTimeoutTime.value(set);
+                try {
+                    timeout = Optional.of(TimeValue.fromString(value));
+                } catch (IllegalArgumentException iae) {
+                    throw new CommandLineOptionException(iae.getMessage(), iae);
+                }
+            } else {
+                timeout = Optional.none();
+            }
+
             if (set.has(optThreads)) {
                 String v = optThreads.value(set);
                 if (v.equalsIgnoreCase("max")) {
@@ -665,4 +680,8 @@
         return new HashSet<Mode>(benchMode);
     }
 
+    @Override
+    public Optional<TimeValue> getTimeout() {
+        return timeout;
+    }
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/Options.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/Options.java	Thu Sep 11 13:40:39 2014 +0400
@@ -238,4 +238,10 @@
      */
     Optional<Collection<String>> getParameter(String name);
 
+    /**
+     * Timeout: how long to wait for an iteration to complete.
+     * @return duration
+     */
+    Optional<TimeValue> getTimeout();
+
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/OptionsBuilder.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/OptionsBuilder.java	Thu Sep 11 13:40:39 2014 +0400
@@ -648,4 +648,26 @@
         params.putAll(name, Arrays.asList(values));
         return this;
     }
+
+    // ---------------------------------------------------------------------------
+
+    private Optional<TimeValue> timeout = Optional.none();
+
+    @Override
+    public ChainedOptionsBuilder timeout(TimeValue value) {
+        this.timeout = Optional.of(value);
+        return this;
+    }
+
+    @Override
+    public Optional<TimeValue> getTimeout() {
+        if (otherOptions != null) {
+            return timeout.orAnother(otherOptions.getTimeout());
+        } else {
+            return timeout;
+        }
+    }
+
+    // ---------------------------------------------------------------------------
+
 }
--- a/jmh-core/src/test/java/org/openjdk/jmh/results/TestAggregateResult.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/test/java/org/openjdk/jmh/results/TestAggregateResult.java	Thu Sep 11 13:40:39 2014 +0400
@@ -30,7 +30,6 @@
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
 import org.openjdk.jmh.runner.IterationType;
-import org.openjdk.jmh.runner.Runner;
 import org.openjdk.jmh.runner.options.TimeValue;
 import org.openjdk.jmh.util.Utils;
 
@@ -54,7 +53,8 @@
                         new IterationParams(IterationType.WARMUP, 1, TimeValue.seconds(1), 1),
                         new IterationParams(IterationType.MEASUREMENT, 1, TimeValue.seconds(1), 1),
                         Mode.Throughput, null, TimeUnit.SECONDS, 1,
-                        Utils.getCurrentJvm(), Collections.<String>emptyList()),
+                        Utils.getCurrentJvm(), Collections.<String>emptyList(),
+                        TimeValue.days(1)),
                 new IterationParams(IterationType.MEASUREMENT, 1, TimeValue.days(1), 1)
         );
         for (double d : values) {
--- a/jmh-core/src/test/java/org/openjdk/jmh/results/format/ResultFormatTest.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/test/java/org/openjdk/jmh/results/format/ResultFormatTest.java	Thu Sep 11 13:40:39 2014 +0400
@@ -81,7 +81,8 @@
                     ps,
                     TimeUnit.SECONDS, 1,
                     Utils.getCurrentJvm(),
-                    Collections.<String>emptyList());
+                    Collections.<String>emptyList(),
+                    TimeValue.days(1));
 
             Collection<BenchmarkResult> benchmarkResults = new ArrayList<BenchmarkResult>();
             for (int f = 0; f < r.nextInt(10); f++) {
--- a/jmh-core/src/test/java/org/openjdk/jmh/runner/RunnerTest.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/test/java/org/openjdk/jmh/runner/RunnerTest.java	Thu Sep 11 13:40:39 2014 +0400
@@ -61,7 +61,8 @@
                 new IterationParams(IterationType.WARMUP,      1, TimeValue.seconds(1), 1),
                 new IterationParams(IterationType.MEASUREMENT, 1, TimeValue.seconds(1), 1),
                 Mode.Throughput, null, TimeUnit.SECONDS, 1,
-                Utils.getCurrentJvm(), Collections.<String>emptyList());
+                Utils.getCurrentJvm(), Collections.<String>emptyList(),
+                TimeValue.days(1));
         String[] command = blade.getSeparateExecutionCommand(bp, DUMMY_HOST, DUMMY_PORT, Collections.<String>emptyList(), Collections.<String>emptyList());
 
         // expecting 1 compile command file
@@ -89,7 +90,8 @@
                 new IterationParams(IterationType.WARMUP,      1, TimeValue.seconds(1), 1),
                 new IterationParams(IterationType.MEASUREMENT, 1, TimeValue.seconds(1), 1),
                 Mode.Throughput, null, TimeUnit.SECONDS, 1,
-                Utils.getCurrentJvm(), Arrays.asList(CompilerHints.XX_COMPILE_COMMAND_FILE + tempHints));
+                Utils.getCurrentJvm(), Arrays.asList(CompilerHints.XX_COMPILE_COMMAND_FILE + tempHints),
+                TimeValue.days(1));
         String[] command = blade.getSeparateExecutionCommand(bp, DUMMY_HOST, DUMMY_PORT, Collections.<String>emptyList(), Collections.<String>emptyList());
 
         // expecting 1 compile command file
@@ -122,7 +124,8 @@
                 new IterationParams(IterationType.MEASUREMENT, 1, TimeValue.seconds(1), 1),
                 Mode.Throughput, null, TimeUnit.SECONDS, 1,
                 Utils.getCurrentJvm(),
-                Arrays.asList(CompilerHints.XX_COMPILE_COMMAND_FILE + tempHints1, CompilerHints.XX_COMPILE_COMMAND_FILE + tempHints2));
+                Arrays.asList(CompilerHints.XX_COMPILE_COMMAND_FILE + tempHints1, CompilerHints.XX_COMPILE_COMMAND_FILE + tempHints2),
+                TimeValue.days(1));
         String[] command = blade.getSeparateExecutionCommand(bp, DUMMY_HOST, DUMMY_PORT, Collections.<String>emptyList(), Collections.<String>emptyList());
 
         // expecting 1 compile command file
--- a/jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestOptions.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestOptions.java	Thu Sep 11 13:40:39 2014 +0400
@@ -533,4 +533,16 @@
         }
     }
 
+    @Test
+    public void testTimeout() throws Exception {
+        CommandLineOptions cmdLine = new CommandLineOptions("-to", "34ms");
+        Options builder = new OptionsBuilder().timeout(TimeValue.milliseconds(34)).build();
+        Assert.assertEquals(builder.getTimeout(), cmdLine.getTimeout());
+    }
+
+    @Test
+    public void testTimeout_Default() throws Exception {
+        Assert.assertEquals(EMPTY_BUILDER.getTimeout(), EMPTY_CMDLINE.getTimeout());
+    }
+
 }
--- a/jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestParentOptions.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestParentOptions.java	Thu Sep 11 13:40:39 2014 +0400
@@ -616,4 +616,25 @@
         }
     }
 
+    @Test
+    public void testTimeout_Empty() throws Exception {
+        Options parent = new OptionsBuilder().build();
+        Options builder = new OptionsBuilder().parent(parent).build();
+        Assert.assertFalse(builder.getTimeout().hasValue());
+    }
+
+    @Test
+    public void testTimeout_Parent() throws Exception {
+        Options parent = new OptionsBuilder().timeout(TimeValue.hours(42)).build();
+        Options builder = new OptionsBuilder().parent(parent).build();
+        Assert.assertEquals(TimeValue.hours(42), builder.getTimeout().get());
+    }
+
+    @Test
+    public void testTimeout_Merged() throws Exception {
+        Options parent = new OptionsBuilder().timeout(TimeValue.hours(42)).build();
+        Options builder = new OptionsBuilder().parent(parent).timeout(TimeValue.days(42)).build();
+        Assert.assertEquals(TimeValue.days(42), builder.getTimeout().get());
+    }
+
 }
--- a/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_30_Interrupts.java	Wed Sep 10 18:59:26 2014 +0400
+++ b/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_30_Interrupts.java	Thu Sep 11 13:40:39 2014 +0400
@@ -28,6 +28,7 @@
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.Group;
 import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.Setup;
 import org.openjdk.jmh.annotations.State;
@@ -35,11 +36,14 @@
 import org.openjdk.jmh.runner.RunnerException;
 import org.openjdk.jmh.runner.options.Options;
 import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
 
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 @BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
 @State(Scope.Group)
 public class JMHSample_30_Interrupts {
 
@@ -57,6 +61,8 @@
      * to interrupts well, and therefore we can rely on JMH to terminate the
      * measurement for us. JMH will notify users about the interrupt actions
      * nevertheless, so users can see if those interrupts affected the measurement.
+     * JMH will start issuing interrupts after the default or user-specified timeout
+     * had been reached.
      *
      * This is a variant of org.openjdk.jmh.samples.JMHSample_18_Control, but without
      * the explicit control objects. This example is suitable for the methods which
@@ -89,8 +95,8 @@
      *
      * a) Via the command line:
      *    $ mvn clean install
-     *    $ java -jar target/benchmarks.jar JMHSample_30 -wi 5 -i 5 -t 2 -f 5
-     *    (we requested 5 warmup iterations, 5 iterations, 2 threads, and 5 forks)
+     *    $ java -jar target/benchmarks.jar JMHSample_30 -wi 5 -i 5 -t 2 -f 5 -to 5
+     *    (we requested 5 warmup iterations, 5 iterations, 2 threads, 5 forks, and 5 sec timeout)
      *
      * b) Via the Java API:
      *    (see the JMH homepage for possible caveats when running from IDE:
@@ -104,6 +110,7 @@
                 .measurementIterations(5)
                 .threads(2)
                 .forks(5)
+                .timeout(TimeValue.seconds(5))
                 .build();
 
         new Runner(opt).run();