+ * Matches all functions by default, i.e. default pattern is {@code .*}. + * + * @return the regular expression used to match function names. + */ + String value() default ".*"; +} diff --git a/src/main/java/jenkins/benchmark/jmh/JmhBenchmarkState.java b/src/main/java/jenkins/benchmark/jmh/JmhBenchmarkState.java new file mode 100644 index 00000000..7b262f63 --- /dev/null +++ b/src/main/java/jenkins/benchmark/jmh/JmhBenchmarkState.java @@ -0,0 +1,158 @@ +package jenkins.benchmark.jmh; + +import hudson.model.Hudson; +import hudson.model.RootAction; +import hudson.security.ACL; +import jenkins.model.Jenkins; +import jenkins.model.JenkinsLocationConfiguration; +import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.eclipse.jetty.server.Server; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TemporaryDirectoryAllocator; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; + +import javax.annotation.CheckForNull; +import javax.servlet.ServletContext; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Standard benchmark {@link State} for JMH when a Jenkins instance is required. + *
+ * To use a Jenkins instance in your benchmark, your class containing benchmarks should have a public static inner + * class that extends this class and should be annotated with {@link JmhBenchmark} to allow it to be automatically + * discovered by {@link BenchmarkFinder}. To configure the instance, use {@link #setup()}. + * + * @see #setup() + * @see #tearDown() + * @see BenchmarkFinder + * @since TODO + */ +@State(Scope.Benchmark) +public abstract class JmhBenchmarkState implements RootAction { + private static final Logger LOGGER = Logger.getLogger(JmhBenchmarkState.class.getName()); + private static final String contextPath = "/jenkins"; + + private final TemporaryDirectoryAllocator temporaryDirectoryAllocator = new TemporaryDirectoryAllocator(); + private final MutableInt localPort = new MutableInt(); + + private Jenkins jenkins = null; + private Server server = null; + + /** + * Sets up the temporary Jenkins instance for benchmarks. + *
+ * One Jenkins instance is created for each fork of the benchmark.
+ *
+ * @throws Exception if unable to start the instance.
+ */
+ @Setup(org.openjdk.jmh.annotations.Level.Trial)
+ public final void setupJenkins() throws Exception {
+ // Set the jenkins.install.InstallState TEST to emulate
+ // org.jvnet.hudson.test.JenkinsRule behaviour and avoid manual
+ // security setup as in a default installation.
+ System.setProperty("jenkins.install.state", "TEST");
+ launchInstance();
+ ACL.impersonate(ACL.SYSTEM);
+ setup();
+ }
+
+ /**
+ * Terminates the jenkins instance after the benchmark has completed its execution.
+ * Run once for each Jenkins that was started.
+ */
+ @TearDown(org.openjdk.jmh.annotations.Level.Trial)
+ public final void terminateJenkins() {
+ try {
+ tearDown();
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Exception occurred during tearDown of Jenkins instance", e);
+ } finally {
+ JenkinsRule._stopJenkins(server, null, jenkins);
+ try {
+ temporaryDirectoryAllocator.dispose();
+ } catch (InterruptedException | IOException e) {
+ LOGGER.log(Level.WARNING, "Unable to dispose temporary Jenkins directory" +
+ "that was started for benchmark", e);
+ }
+ }
+ }
+
+ private void launchInstance() throws Exception {
+ ImmutablePair
+ * The instance can also be obtained using {@link Jenkins#getInstanceOrNull()}
+ *
+ * @return the Jenkins instance started for the benchmark.
+ */
+ public Jenkins getJenkins() {
+ return jenkins;
+ }
+
+ /**
+ * Override to setup resources required for the benchmark.
+ *
+ * Runs before the benchmarks are run. At this state, the Jenkins instance
+ * is ready to be worked upon and is available using {@link #getJenkins()}.
+ * Does nothing by default.
+ */
+ public void setup() throws Exception {
+ // noop
+ }
+
+ /**
+ * Override to perform cleanup of resource initialized during setup.
+ *
+ * Run before the Jenkins instance is terminated. Does nothing by default.
+ */
+ public void tearDown() {
+ // noop
+ }
+
+ @CheckForNull
+ @Override
+ public String getIconFileName() {
+ return null;
+ }
+
+ @CheckForNull
+ @Override
+ public String getDisplayName() {
+ return null;
+ }
+
+ @CheckForNull
+ @Override
+ public String getUrlName() {
+ return "self";
+ }
+}
diff --git a/src/main/java/org/jvnet/hudson/test/JenkinsRule.java b/src/main/java/org/jvnet/hudson/test/JenkinsRule.java
index 739c838b..7ac7aa94 100644
--- a/src/main/java/org/jvnet/hudson/test/JenkinsRule.java
+++ b/src/main/java/org/jvnet/hudson/test/JenkinsRule.java
@@ -159,6 +159,8 @@
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
import java.util.jar.Manifest;
import java.util.logging.Filter;
import java.util.logging.Level;
@@ -185,6 +187,7 @@
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.LoginService;
@@ -410,17 +413,9 @@ public void before() throws Throwable {
f.set(null,null);
throw e;
}
- jenkins.setNoUsageStatistics(true); // collecting usage stats from tests are pointless.
-
- jenkins.setCrumbIssuer(new TestCrumbIssuer());
-
- jenkins.servletContext.setAttribute("app",jenkins);
- jenkins.servletContext.setAttribute("version","?");
- WebAppMain.installExpressionFactory(new ServletContextEvent(jenkins.servletContext));
-
- // set a default JDK to be the one that the harness is using.
- jenkins.getJDKs().add(new JDK("default",System.getProperty("java.home")));
+ jenkins.setCrumbIssuer(new TestCrumbIssuer()); // TODO: Move to _configureJenkinsForTest after JENKINS-55240
+ _configureJenkinsForTest(jenkins);
configureUpdateCenter();
// expose the test instance as a part of URL tree.
@@ -430,11 +425,45 @@ public void before() throws Throwable {
JenkinsLocationConfiguration.get().setUrl(getURL().toString());
}
+ /**
+ * Configures a Jenkins instance for test.
+ *
+ * @param jenkins jenkins instance which has to be configured
+ * @throws Exception if unable to configure
+ * @since TODO
+ */
+ public static void _configureJenkinsForTest(Jenkins jenkins) throws Exception {
+ jenkins.setNoUsageStatistics(true); // collecting usage stats from tests is pointless.
+ jenkins.servletContext.setAttribute("app", jenkins);
+ jenkins.servletContext.setAttribute("version", "?");
+ WebAppMain.installExpressionFactory(new ServletContextEvent(jenkins.servletContext));
+
+ // set a default JDK to be the one that the harness is using.
+ jenkins.getJDKs().add(new JDK("default", System.getProperty("java.home")));
+ }
+
+ private static void dumpThreads() {
+ ThreadInfo[] threadInfos = Functions.getThreadInfos();
+ Functions.ThreadGroupMap m = Functions.sortThreadsAndGetGroupMap(threadInfos);
+ for (ThreadInfo ti : threadInfos) {
+ System.err.println(Functions.dumpThreadInfo(ti, m));
+ }
+ }
+
/**
* Configures the update center setting for the test.
* By default, we load updates from local proxy to avoid network traffic as much as possible.
*/
protected void configureUpdateCenter() throws Exception {
+ _configureUpdateCenter(jenkins);
+ }
+
+ /**
+ * Internal method used to configure update center to avoid network traffic.
+ * @param jenkins the Jenkins to configure
+ * @since TODO
+ */
+ public static void _configureUpdateCenter(Jenkins jenkins) throws Exception {
final String updateCenterUrl;
jettyLevel(Level.WARNING);
try {
@@ -451,14 +480,6 @@ protected void configureUpdateCenter() throws Exception {
sites.clear();
sites.add(new UpdateSite("default", updateCenterUrl));
}
-
- private static void dumpThreads() {
- ThreadInfo[] threadInfos = Functions.getThreadInfos();
- Functions.ThreadGroupMap m = Functions.sortThreadsAndGetGroupMap(threadInfos);
- for (ThreadInfo ti : threadInfos) {
- System.err.println(Functions.dumpThreadInfo(ti, m));
- }
- }
/**
* Override to tear down your specific external resource.
@@ -483,25 +504,7 @@ public void after() throws Exception {
clients.clear();
} finally {
- jettyLevel(Level.WARNING);
- try {
- server.stop();
- } catch (Exception e) {
- // ignore
- } finally {
- jettyLevel(Level.INFO);
- }
- for (LenientRunnable r : tearDowns)
- try {
- r.run();
- } catch (Exception e) {
- // ignore
- }
-
- if (jenkins!=null)
- jenkins.cleanUp();
- ExtensionList.clearLegacyInstances();
- DescriptorExtensionList.clearLegacyInstances();
+ _stopJenkins(server, tearDowns, jenkins);
try {
env.dispose();
@@ -524,6 +527,46 @@ public void after() throws Exception {
}
}
+ /**
+ * Internal method to stop Jenkins instance.
+ *
+ * @param server server on which Jenkins is running.
+ * @param tearDowns tear down methods for tests
+ * @param jenkins the jenkins instance
+ * @since TODO
+ */
+ public static void _stopJenkins(Server server, List