changeset 1442:f797116d4991

7902106: jmh.separateClasspathJAR option to handle benchmarks with long classpath Contributed-by: David Karnok <akarnokd@gmail.com>
author shade
date Mon, 22 Jan 2018 18:35:26 +0100
parents a5079769b73b
children ef50cc696984
files jmh-core-it/src/test/java/org/openjdk/jmh/it/fork/ForkSeparateClasspathJARTest.java jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java
diffstat 2 files changed, 153 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/fork/ForkSeparateClasspathJARTest.java	Mon Jan 22 18:35:26 2018 +0100
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2017, Red Hat Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.it.fork;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.it.Fixtures;
+import org.openjdk.jmh.results.RunResult;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import java.util.concurrent.TimeUnit;
+
+@Fork(1)
+@Warmup(iterations = 0)
+@Measurement(iterations = 1, time = 100, timeUnit = TimeUnit.MILLISECONDS)
+public class ForkSeparateClasspathJARTest {
+
+    @Benchmark
+    public void test() {
+        Fixtures.work();
+    }
+
+    @Test
+    public void withEnabled() throws RunnerException {
+        System.setProperty("jmh.separateClasspathJAR", "true");
+
+        Options opt = new OptionsBuilder()
+                .include(Fixtures.getTestMask(this.getClass()))
+                .shouldFailOnError(true)
+                .build();
+        RunResult result = new Runner(opt).runSingle();
+
+        Assert.assertEquals(1, result.getAggregatedResult().getPrimaryResult().getSampleCount());
+    }
+
+    @Test
+    public void withDisabled() throws RunnerException {
+        System.setProperty("jmh.separateClasspathJAR", "false");
+
+        Options opt = new OptionsBuilder()
+                .include(Fixtures.getTestMask(this.getClass()))
+                .shouldFailOnError(true)
+                .build();
+        RunResult result = new Runner(opt).runSingle();
+
+        Assert.assertEquals(1, result.getAggregatedResult().getPrimaryResult().getSampleCount());
+    }
+
+}
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java	Mon Jan 22 18:10:17 2018 +0100
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java	Mon Jan 22 18:35:26 2018 +0100
@@ -45,8 +45,11 @@
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
 import java.nio.channels.OverlappingFileLockException;
+import java.nio.file.Path;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
+import java.util.jar.*;
+import java.util.zip.*;
 
 /**
  * Runner executes JMH benchmarks.
@@ -813,12 +816,7 @@
         CompilerHints.addCompilerHints(command);
 
         // assemble final process command
-        command.add("-cp");
-        if (Utils.isWindows()) {
-            command.add('"' + System.getProperty("java.class.path") + '"');
-        } else {
-            command.add(System.getProperty("java.class.path"));
-        }
+        addClasspath(command);
 
         command.add(ForkedMain.class.getName());
 
@@ -838,16 +836,81 @@
         command.add(jvm);
 
         // assemble final process command
-        command.add("-cp");
-        if (Utils.isWindows()) {
-            command.add('"' + System.getProperty("java.class.path") + '"');
-        } else {
-            command.add(System.getProperty("java.class.path"));
-        }
+        addClasspath(command);
 
         command.add(PrintPropertiesMain.class.getName());
 
         return command;
     }
 
+    private void addClasspath(List<String> command) {
+        command.add("-cp");
+
+        String cpProp = System.getProperty("java.class.path");
+        File tmpFile = null;
+
+        String jvmargs = ""
+                + options.getJvmArgs().orElse(Collections.<String>emptyList())
+                + options.getJvmArgsPrepend().orElse(Collections.<String>emptyList())
+                + options.getJvmArgsAppend().orElse(Collections.<String>emptyList());
+
+        // The second (creepy) test is for the cases when external plugins are not supplying
+        // the options properly. Looking at you, JMH Gradle plugin. In this case, we explicitly
+        // check if the option is provided by the user.
+
+        if (Boolean.getBoolean("jmh.separateClasspathJAR")
+                || jvmargs.contains("jmh.separateClasspathJAR=true")) {
+
+            // Classpath can be too long and overflow the command line length.
+            // Looking at you, Windows.
+            //
+            // The trick is to generate the JAR file with appropriate Class-Path manifest entry,
+            // and link it. The complication is that Class-Path entry paths are specified relative
+            // to JAR file loaded, which is probably somewhere in java.io.tmpdir, outside of current
+            // directory. Therefore, we have to relativize the paths to all the JAR entries.
+
+            try {
+                tmpFile = FileUtils.tempFile("classpath.jar");
+                Path tmpFileDir = tmpFile.toPath().getParent();
+
+                StringBuilder sb = new StringBuilder();
+                for (String cp : cpProp.split(File.pathSeparator)) {
+                    String rel = tmpFileDir.relativize(new File(cp).getAbsoluteFile().toPath()).toString();
+                    sb.append(rel.replace('\\', '/').replace(" ", "%20"));
+                    if (!cp.endsWith(".jar")) {
+                        sb.append('/');
+                    }
+                    sb.append(" ");
+                }
+                String classPath = sb.toString().trim();
+
+                Manifest manifest = new Manifest();
+                Attributes attrs = manifest.getMainAttributes();
+                attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
+                attrs.putValue("Class-Path", classPath);
+
+                try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(tmpFile), manifest)) {
+                    jos.putNextEntry(new ZipEntry("META-INF/"));
+                }
+            } catch (IOException ex) {
+                // Something is wrong in file generation, give up and fall-through to usual thing
+                tmpFile = null;
+            }
+        }
+
+        if (tmpFile != null) {
+            if (Utils.isWindows()) {
+                command.add("\"" + tmpFile.getAbsolutePath() + "\"");
+            } else {
+                command.add(tmpFile.getAbsolutePath());
+            }
+        } else {
+            if (Utils.isWindows()) {
+                command.add('"' + cpProp + '"');
+            } else {
+                command.add(cpProp);
+            }
+        }
+    }
+
 }