diff --git a/README.md b/README.md
index 8e9435a786..9349124bae 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,20 @@ If you had a `jar:test-jar` execution, delete it and add to `properties`:
You can configure your plugin to treat every commit as a release candidate.
See [Incrementals](https://github.com/jenkinsci/incrementals-tools) for details.
+## Running Benchmarks
+
+To run JMH benchmarks from JUnit tests, you must run you must activate the `benchmark`
+profile. For example:
+```bash
+mvn -Dbenchmark test
+```
+When the `benchmark` property is set, no tests apart from JMH benchmarks will be run.
+The names of the classes containing the benchmark runners should either begin with or
+end with the the word `Benchmark`. For example, `FooBenchmark` and `BenchmarkFoo` will
+be detected when using `-Dbenchmark`, however, `FooBar` will be ignored.
+
+See also: [documentation for JMH benchmarks](https://github.com/jenkinsci/jenkins-test-harness/blob/master/docs/jmh-benchmarks.adoc)
+
## Baselines
It is handy to be able to select different Jenkins baselines with a Maven profile.
diff --git a/pom.xml b/pom.xml
index be0def0432..f5d74dbce5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,7 +52,7 @@
${jenkins.version}
${jenkins.version}
- 2.49
+ 2.50
3.5
1.17
you-must-override-the-java.level-property
@@ -1493,5 +1493,28 @@
+
+
+ jmh-benchmark
+
+
+ benchmark
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/*Benchmark.java
+ **/Benchmark*.java
+
+
+
+
+
+
diff --git a/src/it/benchmark/invoker.properties b/src/it/benchmark/invoker.properties
new file mode 100644
index 0000000000..85dbb8329f
--- /dev/null
+++ b/src/it/benchmark/invoker.properties
@@ -0,0 +1 @@
+invoker.goals=-Dbenchmark clean test
diff --git a/src/it/benchmark/pom.xml b/src/it/benchmark/pom.xml
new file mode 100644
index 0000000000..e56be8c582
--- /dev/null
+++ b/src/it/benchmark/pom.xml
@@ -0,0 +1,31 @@
+
+
+ 4.0.0
+
+ org.jenkins-ci.plugins
+ plugin
+ @project.version@
+
+
+ org.jenkins-ci.plugins.its
+ benchmark-it
+ 1.0-SNAPSHOT
+ jar
+
+ 2.60.3
+ 8
+
+
+
+ repo.jenkins-ci.org
+ https://repo.jenkins-ci.org/public/
+
+
+
+
+ repo.jenkins-ci.org
+ https://repo.jenkins-ci.org/public/
+
+
+
diff --git a/src/it/benchmark/postbuild.groovy b/src/it/benchmark/postbuild.groovy
new file mode 100644
index 0000000000..9e3e2089d9
--- /dev/null
+++ b/src/it/benchmark/postbuild.groovy
@@ -0,0 +1,3 @@
+// check if the benchmark was run
+def file = new File(basedir, 'jmh-report.json')
+assert file.exists()
diff --git a/src/it/benchmark/src/test/java/benchmark/BenchmarkRunner.java b/src/it/benchmark/src/test/java/benchmark/BenchmarkRunner.java
new file mode 100644
index 0000000000..0b6aeda151
--- /dev/null
+++ b/src/it/benchmark/src/test/java/benchmark/BenchmarkRunner.java
@@ -0,0 +1,32 @@
+package benchmark;
+
+import jenkins.benchmark.jmh.BenchmarkFinder;
+import org.junit.Test;
+import org.openjdk.jmh.results.format.ResultFormatType;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import java.util.concurrent.TimeUnit;
+
+public class BenchmarkRunner {
+ @Test
+ public void runThis() throws Exception {
+ // number of iterations is kept to a minimum just to verify that the benchmarks work without spending extra
+ // time during builds.
+ ChainedOptionsBuilder optionsBuilder =
+ new OptionsBuilder()
+ .forks(1)
+ .warmupIterations(1)
+ .warmupBatchSize(1)
+ .measurementIterations(1)
+ .measurementBatchSize(1)
+ .shouldFailOnError(true)
+ .result("jmh-report.json")
+ .timeUnit(TimeUnit.MICROSECONDS)
+ .resultFormat(ResultFormatType.JSON);
+ BenchmarkFinder finder = new BenchmarkFinder(this.getClass().getPackage().getName());
+ finder.findBenchmarks(optionsBuilder);
+ new Runner(optionsBuilder.build()).run();
+ }
+}
diff --git a/src/it/benchmark/src/test/java/benchmark/DontRunMeTest.java b/src/it/benchmark/src/test/java/benchmark/DontRunMeTest.java
new file mode 100644
index 0000000000..2dfa2c91c0
--- /dev/null
+++ b/src/it/benchmark/src/test/java/benchmark/DontRunMeTest.java
@@ -0,0 +1,10 @@
+package benchmark;
+
+import org.junit.Test;
+
+public class DontRunMeTest {
+ @Test
+ public void dontRunThis() throws Exception {
+ throw new Exception("Normal tests should not be run with benchmarks.");
+ }
+}
diff --git a/src/it/benchmark/src/test/java/benchmark/SampleBenchmark.java b/src/it/benchmark/src/test/java/benchmark/SampleBenchmark.java
new file mode 100644
index 0000000000..49e5599fc1
--- /dev/null
+++ b/src/it/benchmark/src/test/java/benchmark/SampleBenchmark.java
@@ -0,0 +1,18 @@
+package benchmark;
+
+import jenkins.benchmark.jmh.JmhBenchmark;
+import jenkins.benchmark.jmh.JmhBenchmarkState;
+import org.openjdk.jmh.annotations.Benchmark;
+
+import java.io.IOException;
+
+@JmhBenchmark
+public class SampleBenchmark {
+ public static class MyState extends JmhBenchmarkState {
+ }
+
+ @Benchmark
+ public void benchmark(MyState state) throws IOException {
+ state.getJenkins().setSystemMessage("Hello world");
+ }
+}