[PATCH] Add JMH and JDK version and effective JVM path to the JSON output

Jens Wilke jw_list at headissue.com
Wed Apr 26 08:01:53 UTC 2017


On Montag, 17. April 2017 16:39:07 ICT Aleksey Shipilev wrote:
> Hi Jens,
> 
> Sorry for late reply: conference/vacation weeks.

No problem. Here is the next version of the patch. All your comments are accounted for. Remarks:

The text is "please consider updating" and "released x days ago" is gone in the text output. I did refactor the doubled code for printing the header into
TextReportFormat.startBenchmark.  Printing out the some information would require to add the JMH release date to the BenchmarkParams to have the information available. That feels rather odd. I would advocate for simpler code and drop that feature, but I am open for anything.
The header represents the testing environment, which means, printing each day something different isn't so nice anyways.

The escaping gets rather wild. I tried my best to make sure that arbitrary string contents pass through unharmed.

More comments can be found in the code.

Enjoy,

Jens

--- 
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/infra/BenchmarkParams.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/infra/BenchmarkParams.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/main/java/org/openjdk/jmh/infra/BenchmarkParams.java	Wed Apr 26 14:53:40 2017 +0700
@@ -26,10 +26,11 @@
 
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.runner.WorkloadParams;
 import org.openjdk.jmh.runner.options.TimeValue;
 import org.openjdk.jmh.util.Utils;
+import org.openjdk.jmh.util.Version;
 
 import java.io.Serializable;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -67,20 +68,21 @@
                              int threads, int[] threadGroups, Collection<String> threadGroupLabels,
                              int forks, int warmupForks,
                              IterationParams warmup, IterationParams measurement,
                              Mode mode, WorkloadParams params,
                              TimeUnit timeUnit, int opsPerInvocation,
-                             String jvm, Collection<String> jvmArgs,
-                             TimeValue timeout) {
+                             String jvm, Collection<String> jvmArgs, String jdkVersion, String vmVersion,
+                             TimeValue timeout, String jmhVersion) {
         super(benchmark, generatedTarget, synchIterations,
                 threads, threadGroups, threadGroupLabels,
                 forks, warmupForks,
                 warmup, measurement,
                 mode, params,
                 timeUnit, opsPerInvocation,
-                jvm, jvmArgs, timeout);
+                jvm, jvmArgs, jdkVersion, vmVersion, timeout, jmhVersion);
     }
+
 }
 
 abstract class BenchmarkParamsL4 extends BenchmarkParamsL3 {
     private static final long serialVersionUID = -2409216922027695385L;
 
@@ -89,20 +91,20 @@
                              int threads, int[] threadGroups, Collection<String> threadGroupLabels,
                              int forks, int warmupForks,
                              IterationParams warmup, IterationParams measurement,
                              Mode mode, WorkloadParams params,
                              TimeUnit timeUnit, int opsPerInvocation,
-                             String jvm, Collection<String> jvmArgs,
-                             TimeValue timeout) {
+                             String jvm, Collection<String> jvmArgs, String jdkVersion, String vmVersion,
+                             TimeValue timeout, String jmhVersion) {
         super(benchmark, generatedTarget, synchIterations,
                 threads, threadGroups, threadGroupLabels,
                 forks, warmupForks,
                 warmup, measurement,
                 mode, params,
                 timeUnit, opsPerInvocation,
-                jvm, jvmArgs,
-                timeout);
+                jvm, jvmArgs, jdkVersion, vmVersion,
+                timeout, jmhVersion);
     }
 }
 
 abstract class BenchmarkParamsL3 extends BenchmarkParamsL2 {
     private static final long serialVersionUID = -53511295235994554L;
@@ -128,20 +130,20 @@
                              int threads, int[] threadGroups, Collection<String> threadGroupLabels,
                              int forks, int warmupForks,
                              IterationParams warmup, IterationParams measurement,
                              Mode mode, WorkloadParams params,
                              TimeUnit timeUnit, int opsPerInvocation,
-                             String jvm, Collection<String> jvmArgs,
-                             TimeValue timeout) {
+                             String jvm, Collection<String> jvmArgs, String jdkVersion, String vmVersion,
+                             TimeValue timeout, String jmhVersion) {
         super(benchmark, generatedTarget, synchIterations,
                 threads, threadGroups, threadGroupLabels,
                 forks, warmupForks,
                 warmup, measurement,
                 mode, params,
                 timeUnit, opsPerInvocation,
-                jvm, jvmArgs,
-                timeout);
+                jvm, jvmArgs, jdkVersion, vmVersion,
+                timeout, jmhVersion);
     }
 }
 
 abstract class BenchmarkParamsL1 extends BenchmarkParamsL0 {
     private boolean p001, p002, p003, p004, p005, p006, p007, p008;
@@ -183,20 +185,23 @@
     protected final WorkloadParams params;
     protected final TimeUnit timeUnit;
     protected final int opsPerInvocation;
     protected final String jvm;
     protected final Collection<String> jvmArgs;
+    protected final String jdkVersion;
     protected final TimeValue timeout;
+    protected final String jmhVersion;
+    protected final String vmVersion;
 
     public BenchmarkParamsL2(String benchmark, String generatedTarget, boolean synchIterations,
                              int threads, int[] threadGroups, Collection<String> threadGroupLabels,
                              int forks, int warmupForks,
                              IterationParams warmup, IterationParams measurement,
                              Mode mode, WorkloadParams params,
                              TimeUnit timeUnit, int opsPerInvocation,
-                             String jvm, Collection<String> jvmArgs,
-                             TimeValue timeout) {
+                             String jvm, Collection<String> jvmArgs, String jdkVersion, String vmVersion,
+                             TimeValue timeout, String jmhVersion) {
         this.benchmark = benchmark;
         this.generatedTarget = generatedTarget;
         this.synchIterations = synchIterations;
         this.threads = threads;
         this.threadGroups = threadGroups;
@@ -209,11 +214,14 @@
         this.params = params;
         this.timeUnit = timeUnit;
         this.opsPerInvocation = opsPerInvocation;
         this.jvm = jvm;
         this.jvmArgs = jvmArgs;
+        this.jdkVersion = jdkVersion;
+        this.vmVersion = vmVersion;
         this.timeout = timeout;
+        this.jmhVersion = jmhVersion;
     }
 
     /**
      * @return how long to wait for iteration to complete
      */
@@ -331,21 +339,46 @@
      */
     public String generatedBenchmark() {
         return generatedTarget;
     }
 
+    /**
+     * @return JVM executable path
+     */
     public String getJvm() {
         return jvm;
     }
 
     /**
+     * @return JMH version identical to {@link Version#getPlainVersion()}, but output format should
+     *          get there input via bean for testing purposes.
+     */
+    public String getJmhVersion() {
+        return jmhVersion;
+    }
+
+    /**
      * @return JVM options
      */
     public Collection<String> getJvmArgs() {
         return Collections.unmodifiableCollection(jvmArgs);
     }
 
+    /**
+     * @return version information as returned by the effective target JVM,
+     *         via system property {@code java.version} and {@code java.vm.version}
+     */
+    public String getJdkVersion() { return jdkVersion; }
+
+    /**
+     * @return version information as returned by the effective target JVM,
+     *         via system property {@code java.vm.version}
+     */
+    public String getVmVersion() {
+        return vmVersion;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/infra/Blackhole.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/infra/Blackhole.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/main/java/org/openjdk/jmh/infra/Blackhole.java	Wed Apr 26 14:53:40 2017 +0700
@@ -286,10 +286,12 @@
     /**
      * Make any consumed data begone.
      *
      * WARNING: This method should only be called by the infrastructure code, in clearly understood cases.
      * Even though it is public, it is not supposed to be called by users.
+     *
+     * @param challengeResponse arbitrary string
      */
     public void evaporate(String challengeResponse) {
         if (!challengeResponse.equals("Yes, I am Stephen Hawking, and know a thing or two about black holes.")) {
             throw new IllegalStateException("Who are you?");
         }
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/results/Result.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/results/Result.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/Result.java	Wed Apr 26 14:53:40 2017 +0700
@@ -184,13 +184,12 @@
     protected T getZeroResult() {
         return null;
     }
 
     /**
-     * Get derivative results for this result. These do not participate in aggregation,
-     * and computed on the spot from the aggregated result.
-     * @return
+     * @return derivative results for this result. These do not participate in aggregation,
+     *         and computed on the spot from the aggregated result.
      */
     protected Collection<? extends Result> getDerivativeResults() {
         return Collections.emptyList();
     }
 
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/results/format/JSONResultFormat.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/results/format/JSONResultFormat.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/format/JSONResultFormat.java	Wed Apr 26 14:53:40 2017 +0700
@@ -67,14 +67,22 @@
             } else {
                 pw.println(",");
             }
 
             pw.println("{");
+            pw.println("\"jmhVersion\" : \"" + params.getJmhVersion() + "\",");
             pw.println("\"benchmark\" : \"" + params.getBenchmark() + "\",");
             pw.println("\"mode\" : \"" + params.getMode().shortLabel() + "\",");
             pw.println("\"threads\" : " + params.getThreads() + ",");
             pw.println("\"forks\" : " + params.getForks() + ",");
+            pw.println("\"jvm\" : " + toJsonString(params.getJvm()) + ",");
+            // if empty, write an empty array.
+            pw.println("\"jvmArgs\" : [");
+            printStringArray(pw, params.getJvmArgs());
+            pw.println("],");
+            pw.println("\"jdkVersion\" : " + toJsonString(params.getJdkVersion()) + ",");
+            pw.println("\"vmVersion\" : " + toJsonString(params.getVmVersion()) + ",");
             pw.println("\"warmupIterations\" : " + params.getWarmup().getCount() + ",");
             pw.println("\"warmupTime\" : \"" + params.getWarmup().getTime() + "\",");
             pw.println("\"warmupBatchSize\" : " + params.getWarmup().getBatchSize() + ",");
             pw.println("\"measurementIterations\" : " + params.getMeasurement().getCount() + ",");
             pw.println("\"measurementTime\" : \"" + params.getMeasurement().getTime() + "\",");
@@ -232,11 +240,45 @@
         if (d == Double.POSITIVE_INFINITY)
             return "\"+INF\"";
         return String.valueOf(d);
     }
 
-    private String tidy(String s) {
+    /**
+     * Escaping for a JSON string. Does the typical escaping of double quotes and backslash.
+     * Also escapes characters that are handled by the tidying process, so that every ASCII
+     * character makes it correctly into the JSON output. Control characters are filtered.
+     */
+    static String toJsonString(String s) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("\"");
+        for (char c : s.toCharArray()) {
+            if (Character.isISOControl(c)) {
+                continue;
+            }
+            switch (c) {
+                // use & as escape character to escape the tidying
+                case '&': sb.append("&&"); break;
+                // we cannot escape to \\\\ since this would create sequences interpreted by the tidying
+                case '\\': sb.append("&/"); break;
+                case '"': sb.append("&'"); break;
+                // escape spacial chars for the tidying formatting below that might appear in a string
+                case ',': sb.append(";"); break;
+                case '[': sb.append("<"); break;
+                case ']': sb.append(">"); break;
+                case '<': sb.append("&-"); break;
+                case '>': sb.append("&="); break;
+                case ';': sb.append("&:"); break;
+                case '{': sb.append("&("); break;
+                case '}': sb.append("&)"); break;
+                default: sb.append(c);
+            }
+        }
+        sb.append("\"");
+        return sb.toString();
+    }
+
+    static String tidy(String s) {
         s = s.replaceAll("\r", "");
         s = s.replaceAll("\n", " ");
         s = s.replaceAll(",", ",\n");
         s = s.replaceAll("\\{", "{\n");
         s = s.replaceAll("\\[", "[\n");
@@ -248,10 +290,19 @@
 
         // Keep these inline:
         s = s.replaceAll(";", ",");
         s = s.replaceAll("\\<", "[");
         s = s.replaceAll("\\>", "]");
+        // translate back from string escaping to keep all string characters intact
+        s = s.replaceAll("&:", ";");
+        s = s.replaceAll("&'", "\\\\\"");
+        s = s.replaceAll("&\\(", "{");
+        s = s.replaceAll("&\\)", "}");
+        s = s.replaceAll("&-", "<");
+        s = s.replaceAll("&=", ">");
+        s = s.replaceAll("&/", "\\\\\\\\");
+        s = s.replaceAll("&&", "&");
 
         String[] lines = s.split("\n");
 
         StringBuilder sb = new StringBuilder();
 
@@ -290,6 +341,18 @@
         }
         sb.append(rightBracket);
         return sb.toString();
     }
 
+    private static void printStringArray(PrintWriter pw, Collection<String> col) {
+        boolean isFirst = true;
+        for (String e : col) {
+            if (isFirst) {
+                isFirst = false;
+            } else {
+                pw.print(',');
+            }
+            pw.print(toJsonString(e));
+        }
+    }
+
 }
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/runner/BaseRunner.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/BaseRunner.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/BaseRunner.java	Wed Apr 26 14:53:40 2017 +0700
@@ -82,20 +82,10 @@
 
         for (Action action : actionPlan.getActions()) {
             BenchmarkParams params = action.getParams();
             ActionMode mode = action.getMode();
 
-            String realOpts = Utils.join(ManagementFactory.getRuntimeMXBean().getInputArguments(), " ").trim();
-            if (realOpts.isEmpty()) {
-                realOpts = "<none>";
-            }
-
-            out.println("# " + Version.getVersion());
-            out.println("# VM version: " + Utils.getCurrentJvmVersion());
-            out.println("# VM invoker: " + Utils.getCurrentJvm());
-            out.println("# VM options: " + realOpts);
-
             out.startBenchmark(params);
             out.println("");
             etaBeforeBenchmark();
             out.println("# Fork: N/A, test runs in the host VM");
             out.println("# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***");
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/runner/PrintPropertiesMain.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/PrintPropertiesMain.java	Wed Apr 26 14:53:40 2017 +0700
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. 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.runner;
+
+import org.openjdk.jmh.util.Utils;
+
+/**
+ * Main program entry point for exporting the system properties, used for detecting the VM version.
+ */
+class PrintPropertiesMain {
+
+    /**
+     * @param argv Command line arguments
+     */
+    public static void main(String[] argv) throws Exception {
+        Utils.getRecordedSystemProperties().storeToXML(
+                System.out,
+                "JMH properties export for target JVM",
+                "UTF-8");
+    }
+
+}
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java	Wed Apr 26 14:53:40 2017 +0700
@@ -463,10 +463,17 @@
                         Defaults.OPS_PER_INVOCATION));
 
         String jvm = options.getJvm().orElse(
                 benchmark.getJvm().orElse(Utils.getCurrentJvm()));
 
+        Properties targetProperties;
+        if (jvm.equals(Utils.getCurrentJvm())) {
+            targetProperties = Utils.getRecordedSystemProperties();
+        } else {
+            targetProperties = Utils.readPropertiesFromCommand(getPrintPropertiesCommand(jvm));
+        }
+
         Collection<String> jvmArgs = new ArrayList<>();
 
         jvmArgs.addAll(options.getJvmArgsPrepend().orElse(
                 benchmark.getJvmArgsPrepend().orElse(Collections.<String>emptyList())));
 
@@ -477,15 +484,18 @@
                 benchmark.getJvmArgsAppend().orElse(Collections.<String>emptyList())));
 
         TimeValue timeout = options.getTimeout().orElse(
                 benchmark.getTimeout().orElse(Defaults.TIMEOUT));
 
+        String jdkVersion = targetProperties.getProperty("java.version");
+        String vmVersion = targetProperties.getProperty("java.vm.version");
         return new BenchmarkParams(benchmark.getUsername(), benchmark.generatedTarget(), synchIterations,
                 threads, threadGroups, benchmark.getThreadGroupLabels().orElse(Collections.<String>emptyList()),
                 forks, warmupForks,
                 warmup, measurement, benchmark.getMode(), benchmark.getWorkloadParams(), timeUnit, opsPerInvocation,
-                jvm, jvmArgs, timeout);
+                jvm, jvmArgs, jdkVersion, vmVersion,
+                timeout, Version.getPlainVersion());
     }
 
     private List<WorkloadParams> explodeAllParams(BenchmarkListEntry br) throws RunnerException {
         Map<String, String[]> benchParams = br.getParams().orElse(Collections.<String, String[]>emptyMap());
         List<WorkloadParams> ps = new ArrayList<>();
@@ -597,21 +607,10 @@
 
             boolean forcePrint = options.verbosity().orElse(Defaults.VERBOSITY).equalsOrHigherThan(VerboseMode.EXTRA);
             printOut = forcePrint || printOut;
             printErr = forcePrint || printErr;
 
-            List<String> versionString = getVersionMainCommand(params);
-
-            String opts = Utils.join(params.getJvmArgs(), " ");
-            if (opts.trim().isEmpty()) {
-                opts = "<none>";
-            }
-
-            out.println("# " + Version.getVersion());
-            out.print("# VM version: " + Utils.join(Utils.runWith(versionString), "\n"));
-            out.println("# VM invoker: " + params.getJvm());
-            out.println("# VM options: " + opts);
             out.startBenchmark(params);
             out.println("");
 
             int forkCount = params.getForks();
             int warmupForkCount = params.getWarmupForks();
@@ -830,28 +829,25 @@
         command.add(String.valueOf(port));
 
         return command;
     }
 
-    /**
-     * @return
-     */
-    List<String> getVersionMainCommand(BenchmarkParams benchmark) {
+    private List<String> getPrintPropertiesCommand(String jvm) {
         List<String> command = new ArrayList<>();
 
         // use supplied jvm, if given
-        command.add(benchmark.getJvm());
+        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"));
         }
 
-        command.add(VersionMain.class.getName());
+        command.add(PrintPropertiesMain.class.getName());
 
         return command;
     }
 
 }
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/runner/VersionMain.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/VersionMain.java	Tue Mar 28 18:34:51 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-/*
- * Copyright (c) 2005, 2015, Oracle and/or its affiliates. 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.runner;
-
-import org.openjdk.jmh.util.Utils;
-
-/**
- * Main program entry point detecting the VM version.
- */
-class VersionMain {
-
-    /**
-     * @param argv Command line arguments
-     */
-    public static void main(String[] argv) throws Exception {
-        System.err.println(Utils.getCurrentJvmVersion());
-    }
-
-}
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/runner/format/TextReportFormat.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/format/TextReportFormat.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/format/TextReportFormat.java	Wed Apr 26 14:53:40 2017 +0700
@@ -35,10 +35,11 @@
 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 org.openjdk.jmh.util.Utils;
+import org.openjdk.jmh.util.Version;
 
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -54,10 +55,20 @@
         super(out, verbose);
     }
 
     @Override
     public void startBenchmark(BenchmarkParams params) {
+        String opts = Utils.join(params.getJvmArgs(), " ");
+        if (opts.trim().isEmpty()) {
+            opts = "<none>";
+        }
+
+        println("# JMH version: " + params.getJmhVersion());
+        println("# VM version: JDK " + params.getJdkVersion() + ", VM " + params.getVmVersion());
+        println("# VM invoker: " + params.getJvm());
+        println("# VM options: " + opts);
+
         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"));
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/util/FileUtils.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/util/FileUtils.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/main/java/org/openjdk/jmh/util/FileUtils.java	Wed Apr 26 14:53:40 2017 +0700
@@ -45,11 +45,11 @@
      * Creates the temp file, and retains it as long as the reference to it
      * is reachable.
      *
      * @param suffix suffix
      * @return temp file
-     * @throws IOException
+     * @throws IOException if things go crazy
      */
     public static TempFile weakTempFile(String suffix) throws IOException {
         return TEMP_FILE_MANAGER.create(suffix);
     }
 
@@ -61,11 +61,11 @@
      * Creates the temp file with given suffix. The file would be removed
      * on JVM exit, or when caller deletes the file itself.
      *
      * @param suffix suffix
      * @return temporary file
-     * @throws IOException
+     * @throws IOException if things go crazy
      */
     public static File tempFile(String suffix) throws IOException {
         File file = File.createTempFile("jmh", suffix);
         file.deleteOnExit();
         return file;
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/util/Utils.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/util/Utils.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/main/java/org/openjdk/jmh/util/Utils.java	Wed Apr 26 14:53:40 2017 +0700
@@ -473,10 +473,53 @@
         }
         return messages;
     }
 
     /**
+     * We don't access the complete system properties via {@link System#getProperties()} because
+     * this would require read/write permissions to the properties. Just copy the properties we
+     * want to record in the result.
+     *
+     * @return Copy of system properties we want to record in the results.
+     */
+    public static Properties getRecordedSystemProperties() {
+        String[] names = new String[]{"java.version", "java.vm.version"};
+        Properties p = new Properties();
+        for (String i : names) {
+            p.setProperty(i, System.getProperty(i));
+        }
+        return p;
+    }
+
+    public static Properties readPropertiesFromCommand(List<String> cmd) {
+        Properties out = new Properties();
+        try {
+            Process p = new ProcessBuilder(cmd).start();
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+            // drain streams, else we might lock up
+            InputStreamDrainer errDrainer = new InputStreamDrainer(p.getErrorStream(), System.err);
+            InputStreamDrainer outDrainer = new InputStreamDrainer(p.getInputStream(), baos);
+
+            errDrainer.start();
+            outDrainer.start();
+
+            int err = p.waitFor();
+
+            errDrainer.join();
+            outDrainer.join();
+            out.loadFromXML(new ByteArrayInputStream(baos.toByteArray()));
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        } catch (InterruptedException ex) {
+            throw new IllegalStateException(ex);
+        }
+        return out;
+    }
+
+    /**
      * Adapts Iterator for Iterable.
      * Can be iterated only once!
      *
      * @param it iterator
      * @return iterable for given iterator
diff -r 36a2ee9a075e jmh-core/src/main/java/org/openjdk/jmh/util/Version.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/util/Version.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/main/java/org/openjdk/jmh/util/Version.java	Wed Apr 26 14:53:40 2017 +0700
@@ -37,19 +37,45 @@
 
 public class Version {
 
     private static final int UPDATE_INTERVAL = 180;
 
+    /**
+     * @return the version, build date and update hint.
+     */
     public static String getVersion() {
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
 
         printVersion(pw);
         pw.close();
         return sw.toString();
     }
 
+    /**
+     * @return only version, e.g. "1.19", or "-" if the version cannot be determined.
+     */
+    public static String getPlainVersion() {
+        Properties p = new Properties();
+        InputStream s = Version.class.getResourceAsStream("/jmh.properties");
+        if (s == null) {
+            return "-";
+        }
+        try {
+            p.load(s);
+        } catch (IOException e) {
+            return "-";
+        } finally {
+            FileUtils.safelyClose(s);
+        }
+        String version = (String) p.get("jmh.version");
+        if (version == null) {
+            return "-";
+        }
+        return version;
+    }
+
     private static void printVersion(PrintWriter pw) {
         Properties p = new Properties();
         InputStream s = Version.class.getResourceAsStream("/jmh.properties");
 
         if (s == null) {
diff -r 36a2ee9a075e jmh-core/src/test/java/org/openjdk/jmh/results/TestAggregateResult.java
--- a/jmh-core/src/test/java/org/openjdk/jmh/results/TestAggregateResult.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/test/java/org/openjdk/jmh/results/TestAggregateResult.java	Wed Apr 26 14:53:40 2017 +0700
@@ -30,10 +30,11 @@
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
 import org.openjdk.jmh.runner.IterationType;
 import org.openjdk.jmh.runner.options.TimeValue;
 import org.openjdk.jmh.util.Utils;
+import org.openjdk.jmh.util.Version;
 
 import java.util.Collections;
 import java.util.concurrent.TimeUnit;
 
 import static org.junit.Assert.assertEquals;
@@ -53,12 +54,12 @@
                         1, new int[]{1}, Collections.<String>emptyList(),
                         1, 1,
                         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(),
-                        TimeValue.days(1)),
+                        Utils.getCurrentJvm(), Collections.<String>emptyList(), System.getProperty("java.version"), System.getProperty("java.vm.version"),
+                        TimeValue.days(1), Version.getPlainVersion()),
                 new IterationParams(IterationType.MEASUREMENT, 1, TimeValue.days(1), 1),
                 null
         );
         for (double d : values) {
             result.addResult(new ThroughputResult(ResultRole.PRIMARY, "test1", (long) d, 10 * 1000 * 1000, TimeUnit.MILLISECONDS));
diff -r 36a2ee9a075e jmh-core/src/test/java/org/openjdk/jmh/results/format/JSONResultFormatTest.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/test/java/org/openjdk/jmh/results/format/JSONResultFormatTest.java	Wed Apr 26 14:53:40 2017 +0700
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2014, 2015, Oracle and/or its affiliates. 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.results.format;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Extra tests for special cases of the JSON formatter.
+ *
+ * @author Jens Wilke
+ * @see JSONResultFormat
+ */
+public class JSONResultFormatTest {
+
+    @Test
+    public void toJsonString_tidy() {
+        String s = JSONResultFormat.toJsonString("abc,\"{}()\\(\\)[]{}");
+        s = JSONResultFormat.tidy(s);
+        assertEquals("\"abc,\\\"{}()\\\\(\\\\)[]{}\"\n", s);
+    }
+
+    @Test
+    public void toJsonString_tidy_curly() {
+        String s = JSONResultFormat.toJsonString("{}");
+        s = JSONResultFormat.tidy(s);
+        assertEquals("\"{}\"\n", s);
+    }
+
+    @Test
+    public void toJsonString_tidy_curved() {
+        String s = JSONResultFormat.toJsonString("()");
+        s = JSONResultFormat.tidy(s);
+        assertEquals("\"()\"\n", s);
+    }
+
+    @Test
+    public void toJsonString_tidy_escapedDoubleQuote() {
+        String s = JSONResultFormat.toJsonString("\"");
+        s = JSONResultFormat.tidy(s);
+        assertEquals("\"\\\"\"\n", s);
+    }
+
+    @Test
+    public void toJsonString_tidy_escapedEscape() {
+        String s = JSONResultFormat.toJsonString("\\");
+        s = JSONResultFormat.tidy(s);
+        assertEquals("\"\\\\\"\n", s);
+    }
+
+    /**
+     * Check that every ASCII character in a string makes it transparently through
+     * the JSON tidying and formatting process.
+     */
+    @Test
+    public void toJsonString_tidy_asciiTransparent () {
+        for (char i = 32; i < 127; i++) {
+            if (i == '"') {
+                continue;
+            }
+            if (i == '\\') {
+                continue;
+            }
+            String s = JSONResultFormat.toJsonString(Character.toString(i));
+            s = JSONResultFormat.tidy(s);
+            assertEquals("\"" + i + "\"\n", s);
+        }
+    }
+
+}
\ No newline at end of file
diff -r 36a2ee9a075e jmh-core/src/test/java/org/openjdk/jmh/results/format/ResultFormatTest.java
--- a/jmh-core/src/test/java/org/openjdk/jmh/results/format/ResultFormatTest.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/test/java/org/openjdk/jmh/results/format/ResultFormatTest.java	Wed Apr 26 14:53:40 2017 +0700
@@ -44,10 +44,25 @@
  * These tests seal the machine-readable format.
  * Any change to these tests should be discussed with the maintainers first!
  */
 public class ResultFormatTest {
 
+    /**
+     * Use constant dummy for JVM instead of current JVM to compare with golden file.
+     */
+    private static final String JVM_DUMMY = "javadummy";
+
+    /**
+     * Use constant dummy for JVM instead of current JVM to compare with golden file.
+     */
+    private static final String JDK_VERSION_DUMMY = "1.8-dummy";
+
+    private static final String VM_VERSION_DUMMY = "4711";
+
+
+    private static final String JMH_VERSION_DUMMY = "1.18";
+
     private Collection<RunResult> getStub() {
         Collection<RunResult> results = new TreeSet<>(RunResult.DEFAULT_SORT_COMPARATOR);
 
         Random r = new Random(12345);
         Random ar = new Random(12345);
@@ -68,13 +83,14 @@
                     new IterationParams(IterationType.WARMUP,      r.nextInt(1000), TimeValue.seconds(r.nextInt(1000)), 1),
                     new IterationParams(IterationType.MEASUREMENT, r.nextInt(1000), TimeValue.seconds(r.nextInt(1000)), 1),
                     Mode.Throughput,
                     ps,
                     TimeUnit.SECONDS, 1,
-                    Utils.getCurrentJvm(),
+                    JVM_DUMMY,
                     Collections.<String>emptyList(),
-                    TimeValue.days(1));
+                    JDK_VERSION_DUMMY, VM_VERSION_DUMMY,
+                    TimeValue.days(1), JMH_VERSION_DUMMY);
 
             Collection<BenchmarkResult> benchmarkResults = new ArrayList<>();
             for (int f = 0; f < r.nextInt(10); f++) {
                 Collection<IterationResult> iterResults = new ArrayList<>();
                 for (int c = 0; c < r.nextInt(10); c++) {
diff -r 36a2ee9a075e jmh-core/src/test/java/org/openjdk/jmh/runner/RunnerTest.java
--- a/jmh-core/src/test/java/org/openjdk/jmh/runner/RunnerTest.java	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/test/java/org/openjdk/jmh/runner/RunnerTest.java	Wed Apr 26 14:53:40 2017 +0700
@@ -31,10 +31,11 @@
 import org.openjdk.jmh.profile.ExternalProfiler;
 import org.openjdk.jmh.runner.options.OptionsBuilder;
 import org.openjdk.jmh.runner.options.TimeValue;
 import org.openjdk.jmh.util.FileUtils;
 import org.openjdk.jmh.util.Utils;
+import org.openjdk.jmh.util.Version;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
@@ -63,11 +64,12 @@
                 1, 1,
                 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(),
-                TimeValue.days(1));
+                System.getProperty("java.version"), System.getProperty("java.vm.version"),
+                TimeValue.days(1), Version.getPlainVersion());
         List<String> command = blade.getForkedMainCommand(bp, Collections.<ExternalProfiler>emptyList(), DUMMY_HOST, DUMMY_PORT);
 
         // expecting 1 compile command file
         List<String> files = CompilerHints.getCompileCommandFiles(command);
         assertEquals(1, files.size());
@@ -94,11 +96,12 @@
                 1, 1,
                 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.singletonList(CompilerHints.XX_COMPILE_COMMAND_FILE + tempHints),
-                TimeValue.days(1));
+                System.getProperty("java.version"), System.getProperty("java.vm.version"),
+                TimeValue.days(1), Version.getPlainVersion());
         List<String> command = blade.getForkedMainCommand(bp, Collections.<ExternalProfiler>emptyList(), DUMMY_HOST, DUMMY_PORT);
 
         // expecting 1 compile command file
         List<String> files = CompilerHints.getCompileCommandFiles(command);
         assertEquals(1, files.size());
@@ -130,11 +133,12 @@
                 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 + tempHints1, CompilerHints.XX_COMPILE_COMMAND_FILE + tempHints2),
-                TimeValue.days(1));
+                System.getProperty("java.version"), System.getProperty("java.vm.version"),
+                TimeValue.days(1), Version.getPlainVersion());
         List<String> command = blade.getForkedMainCommand(bp, Collections.<ExternalProfiler>emptyList(), DUMMY_HOST, DUMMY_PORT);
 
         // expecting 1 compile command file
         List<String> files = CompilerHints.getCompileCommandFiles(command);
         assertEquals(1, files.size());
diff -r 36a2ee9a075e jmh-core/src/test/resources/org/openjdk/jmh/results/format/output-golden.json
--- a/jmh-core/src/test/resources/org/openjdk/jmh/results/format/output-golden.json	Tue Mar 28 18:34:51 2017 +0200
+++ b/jmh-core/src/test/resources/org/openjdk/jmh/results/format/output-golden.json	Wed Apr 26 14:53:40 2017 +0700
@@ -1,11 +1,17 @@
 [
     {
+        "jmhVersion" : "1.18",
         "benchmark" : "benchmark_0",
         "mode" : "thrpt",
         "threads" : 80,
         "forks" : 828,
+        "jvm" : "javadummy",
+        "jvmArgs" : [
+        ],
+        "jdkVersion" : "1.8-dummy",
+        "vmVersion" : "4711",
         "warmupIterations" : 84,
         "warmupTime" : "375 s",
         "warmupBatchSize" : 1,
         "measurementIterations" : 802,
         "measurementTime" : "501 s",
@@ -214,14 +220,20 @@
                 ]
             }
         }
     },
     {
+        "jmhVersion" : "1.18",
         "benchmark" : "benchmark_1",
         "mode" : "thrpt",
         "threads" : 900,
         "forks" : 364,
+        "jvm" : "javadummy",
+        "jvmArgs" : [
+        ],
+        "jdkVersion" : "1.8-dummy",
+        "vmVersion" : "4711",
         "warmupIterations" : 544,
         "warmupTime" : "409 s",
         "warmupBatchSize" : 1,
         "measurementIterations" : 55,
         "measurementTime" : "398 s",
@@ -313,14 +325,20 @@
                 ]
             }
         }
     },
     {
+        "jmhVersion" : "1.18",
         "benchmark" : "benchmark_2",
         "mode" : "thrpt",
         "threads" : 466,
         "forks" : 677,
+        "jvm" : "javadummy",
+        "jvmArgs" : [
+        ],
+        "jdkVersion" : "1.8-dummy",
+        "vmVersion" : "4711",
         "warmupIterations" : 384,
         "warmupTime" : "105 s",
         "warmupBatchSize" : 1,
         "measurementIterations" : 461,
         "measurementTime" : "96 s",
@@ -474,14 +492,20 @@
                 ]
             }
         }
     },
     {
+        "jmhVersion" : "1.18",
         "benchmark" : "benchmark_3",
         "mode" : "thrpt",
         "threads" : 968,
         "forks" : 581,
+        "jvm" : "javadummy",
+        "jvmArgs" : [
+        ],
+        "jdkVersion" : "1.8-dummy",
+        "vmVersion" : "4711",
         "warmupIterations" : 628,
         "warmupTime" : "207 s",
         "warmupBatchSize" : 1,
         "measurementIterations" : 857,
         "measurementTime" : "438 s",
@@ -667,14 +691,20 @@
                 ]
             }
         }
     },
     {
+        "jmhVersion" : "1.18",
         "benchmark" : "benchmark_4",
         "mode" : "thrpt",
         "threads" : 739,
         "forks" : 670,
+        "jvm" : "javadummy",
+        "jvmArgs" : [
+        ],
+        "jdkVersion" : "1.8-dummy",
+        "vmVersion" : "4711",
         "warmupIterations" : 997,
         "warmupTime" : "651 s",
         "warmupBatchSize" : 1,
         "measurementIterations" : 16,
         "measurementTime" : "763 s",





-- 
"Everything superfluous is wrong!"

   // Jens Wilke - headissue GmbH - Germany
 \//  https://headissue.com


More information about the jmh-dev mailing list