1 /*
   2  * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package org.openjdk.jmh.runner;
  26 
  27 import org.openjdk.jmh.annotations.Mode;
  28 import org.openjdk.jmh.annotations.Threads;
  29 import org.openjdk.jmh.infra.BenchmarkParams;
  30 import org.openjdk.jmh.infra.IterationParams;
  31 import org.openjdk.jmh.profile.ExternalProfiler;
  32 import org.openjdk.jmh.profile.ProfilerException;
  33 import org.openjdk.jmh.profile.ProfilerFactory;
  34 import org.openjdk.jmh.results.*;
  35 import org.openjdk.jmh.results.format.ResultFormatFactory;
  36 import org.openjdk.jmh.runner.format.OutputFormat;
  37 import org.openjdk.jmh.runner.format.OutputFormatFactory;
  38 import org.openjdk.jmh.runner.link.BinaryLinkServer;
  39 import org.openjdk.jmh.runner.options.Options;
  40 import org.openjdk.jmh.runner.options.ProfilerConfig;
  41 import org.openjdk.jmh.runner.options.TimeValue;
  42 import org.openjdk.jmh.runner.options.VerboseMode;
  43 import org.openjdk.jmh.util.*;
  44 
  45 import java.io.*;
  46 import java.lang.management.ManagementFactory;
  47 import java.nio.channels.FileChannel;
  48 import java.nio.channels.FileLock;
  49 import java.nio.channels.OverlappingFileLockException;
  50 import java.util.*;
  51 import java.util.concurrent.TimeUnit;
  52 
  53 /**
  54  * Runner executes JMH benchmarks.
  55  *
  56  * <p>This is the entry point for JMH Java API.</p>
  57  *
  58  * <p>{@link Runner} is not usually reusable. After you execute any method on the {@link Runner}, you should digest
  59  * the results, give up on current {@link Runner}, and instantiate another one. This class may be turned into
  60  * static class in future releases.</p>
  61  */
  62 public class Runner extends BaseRunner {
  63 
  64     private static final int TAIL_LINES_ON_ERROR = Integer.getInteger("jmh.tailLines", 20);
  65     private static final String JMH_LOCK_FILE = System.getProperty("java.io.tmpdir") + "/jmh.lock";
  66     private static final Boolean JMH_LOCK_IGNORE = Boolean.getBoolean("jmh.ignoreLock");
  67 
  68     private final BenchmarkList list;
  69     private int cpuCount;
  70 
  71     /**
  72      * Create runner with the custom OutputFormat.
  73      *
  74      * @param options options to use
  75      * @param format OutputFormat to use
  76      */
  77     public Runner(Options options, OutputFormat format) {
  78         super(options, format);
  79         this.list = BenchmarkList.defaultList();
  80     }
  81 
  82     /**
  83      * Create Runner with the given options.
  84      * This method sets up the {@link org.openjdk.jmh.runner.format.OutputFormat} as
  85      * mandated by options.
  86      * @param options options to use.
  87      */
  88     public Runner(Options options) {
  89         this(options, createOutputFormat(options));
  90     }
  91 
  92     private static OutputFormat createOutputFormat(Options options) {
  93         // sadly required here as the check cannot be made before calling this method in constructor
  94         if (options == null) {
  95             throw new IllegalArgumentException("Options not allowed to be null.");
  96         }
  97 
  98         PrintStream out;
  99         if (options.getOutput().hasValue()) {
 100             try {
 101                 out = new PrintStream(options.getOutput().get());
 102             } catch (FileNotFoundException ex) {
 103                 throw new IllegalStateException(ex);
 104             }
 105         } else {
 106             // Protect the System.out from accidental closing
 107             try {
 108                 out = new UnCloseablePrintStream(System.out, Utils.guessConsoleEncoding());
 109             } catch (UnsupportedEncodingException ex) {
 110                 throw new IllegalStateException(ex);
 111             }
 112         }
 113 
 114         return OutputFormatFactory.createFormatInstance(out, options.verbosity().orElse(Defaults.VERBOSITY));
 115     }
 116 
 117     /**
 118      * Print matching benchmarks into output.
 119      */
 120     public void list() {
 121         Set<BenchmarkListEntry> benchmarks = list.find(out, options.getIncludes(), options.getExcludes());
 122 
 123         out.println("Benchmarks: ");
 124         for (BenchmarkListEntry benchmark : benchmarks) {
 125             out.println(benchmark.getUsername());
 126         }
 127     }
 128 
 129     /**
 130      * Shortcut method for the single benchmark execution.
 131      * This method is handy when Options describe only the single benchmark to run.
 132      *
 133      * @return benchmark result
 134      * @throws RunnerException if more than one benchmark is found, or no results are returned
 135      */
 136     public RunResult runSingle() throws RunnerException {
 137         Set<BenchmarkListEntry> benchmarks = list.find(out, options.getIncludes(), options.getExcludes());
 138 
 139         if (benchmarks.size() == 1) {
 140             Collection<RunResult> values = run();
 141             if (values.size() == 1) {
 142                 return values.iterator().next();
 143             } else {
 144                 throw new RunnerException("No results returned");
 145             }
 146         } else {
 147             throw new RunnerException("More than single benchmark is matching the options");
 148         }
 149     }
 150 
 151     /**
 152      * Run benchmarks.
 153      *
 154      * @return map of benchmark results
 155      * @throws org.openjdk.jmh.runner.RunnerException if something goes wrong
 156      */
 157     public Collection<RunResult> run() throws RunnerException {
 158         FileChannel channel = null;
 159         FileLock lock = null;
 160         try {
 161             channel = new RandomAccessFile(JMH_LOCK_FILE, "rw").getChannel();
 162 
 163             try {
 164                 lock = channel.tryLock();
 165             } catch (OverlappingFileLockException e) {
 166                 // fall-through
 167             }
 168 
 169             if (lock == null) {
 170                 String msg = "Unable to acquire the JMH lock (" + JMH_LOCK_FILE + "): already taken by another JMH instance";
 171                 if (JMH_LOCK_IGNORE) {
 172                     out.println("# WARNING: " + msg + ", ignored by user's request.");
 173                 } else {
 174                     throw new RunnerException("ERROR: " + msg + ", exiting. Use -Djmh.ignoreLock=true to forcefully continue.");
 175                 }
 176             }
 177 
 178             return internalRun();
 179         } catch (IOException e) {
 180             String msg = "Exception while trying to acquire the JMH lock (" + JMH_LOCK_FILE + "): " + e.getMessage();
 181             if (JMH_LOCK_IGNORE) {
 182                 out.println("# WARNING: " + msg + ", ignored by user's request.");
 183                 return internalRun();
 184             } else {
 185                 throw new RunnerException("ERROR: " + msg  + ", exiting. Use -Djmh.ignoreLock=true to forcefully continue.");
 186             }
 187         } finally {
 188             try {
 189                 if (lock != null) {
 190                     lock.release();
 191                 }
 192             } catch (IOException e) {
 193                 // do nothing
 194             }
 195             FileUtils.safelyClose(channel);
 196         }
 197     }
 198 
 199     private Collection<RunResult> internalRun() throws RunnerException {
 200         boolean someProfilersFail = false;
 201         for (ProfilerConfig p : options.getProfilers()) {
 202             try {
 203                 ProfilerFactory.getProfilerOrException(p);
 204             } catch (ProfilerException e) {
 205                 out.println(e.getMessage());
 206                 someProfilersFail = true;
 207             }
 208             if (someProfilersFail) {
 209                 throw new ProfilersFailedException();
 210             }
 211         }
 212 
 213         // If user requested the result file in one way or the other, touch the result file,
 214         // and prepare to write it out after the run.
 215         String resultFile = null;
 216         if (options.getResult().hasValue() || options.getResultFormat().hasValue()) {
 217             resultFile = options.getResult().orElse(
 218                         Defaults.RESULT_FILE_PREFIX + "." +
 219                                 options.getResultFormat().orElse(Defaults.RESULT_FORMAT).toString().toLowerCase()
 220                     );
 221             try {
 222                 FileUtils.touch(resultFile);
 223             } catch (IOException e) {
 224                 throw new RunnerException("Can not touch the result file: " + resultFile);
 225             }
 226         }
 227 
 228         SortedSet<BenchmarkListEntry> benchmarks = list.find(out, options.getIncludes(), options.getExcludes());
 229 
 230         if (benchmarks.isEmpty()) {
 231             out.flush();
 232             out.close();
 233             throw new NoBenchmarksException();
 234         }
 235 
 236         // override the benchmark types;
 237         // this may yield new benchmark records
 238         if (!options.getBenchModes().isEmpty()) {
 239             List<BenchmarkListEntry> newBenchmarks = new ArrayList<BenchmarkListEntry>();
 240             for (BenchmarkListEntry br : benchmarks) {
 241                 for (Mode m : options.getBenchModes()) {
 242                     newBenchmarks.add(br.cloneWith(m));
 243                 }
 244 
 245             }
 246 
 247             benchmarks.clear();
 248             benchmarks.addAll(newBenchmarks);
 249         }
 250 
 251         // clone with all the modes
 252         {
 253             List<BenchmarkListEntry> newBenchmarks = new ArrayList<BenchmarkListEntry>();
 254             for (BenchmarkListEntry br : benchmarks) {
 255                 if (br.getMode() == Mode.All) {
 256                     for (Mode mode : Mode.values()) {
 257                         if (mode == Mode.All) continue;
 258                         newBenchmarks.add(br.cloneWith(mode));
 259                     }
 260                 } else {
 261                     newBenchmarks.add(br);
 262                 }
 263             }
 264 
 265             benchmarks.clear();
 266             benchmarks.addAll(newBenchmarks);
 267         }
 268 
 269         // clone with all parameters
 270         {
 271             List<BenchmarkListEntry> newBenchmarks = new ArrayList<BenchmarkListEntry>();
 272             for (BenchmarkListEntry br : benchmarks) {
 273                 if (br.getParams().hasValue()) {
 274                     for (WorkloadParams p : explodeAllParams(br)) {
 275                         newBenchmarks.add(br.cloneWith(p));
 276                     }
 277                 } else {
 278                     newBenchmarks.add(br);
 279                 }
 280             }
 281             benchmarks.clear();
 282             benchmarks.addAll(newBenchmarks);
 283         }
 284 
 285         Collection<RunResult> results = runBenchmarks(benchmarks);
 286 
 287         // If user requested the result file, write it out.
 288         if (resultFile != null) {
 289             ResultFormatFactory.getInstance(
 290                         options.getResultFormat().orElse(Defaults.RESULT_FORMAT),
 291                         resultFile
 292             ).writeOut(results);
 293 
 294             out.println("");
 295             out.println("Benchmark result is saved to " + resultFile);
 296         }
 297 
 298         out.flush();
 299         out.close();
 300 
 301         return results;
 302     }
 303 
 304     private List<ActionPlan> getActionPlans(Set<BenchmarkListEntry> benchmarks) {
 305         ActionPlan base = new ActionPlan(ActionType.FORKED);
 306 
 307         LinkedHashSet<BenchmarkListEntry> warmupBenches = new LinkedHashSet<BenchmarkListEntry>();
 308 
 309         List<String> warmupMicrosRegexp = options.getWarmupIncludes();
 310         if (warmupMicrosRegexp != null && !warmupMicrosRegexp.isEmpty()) {
 311             warmupBenches.addAll(list.find(out, warmupMicrosRegexp, Collections.<String>emptyList()));
 312         }
 313         if (options.getWarmupMode().orElse(Defaults.WARMUP_MODE).isBulk()) {
 314             warmupBenches.addAll(benchmarks);
 315         }
 316 
 317         for (BenchmarkListEntry wr : warmupBenches) {
 318             base.add(newAction(wr, ActionMode.WARMUP));
 319         }
 320 
 321         ActionPlan embeddedPlan = new ActionPlan(ActionType.EMBEDDED);
 322         embeddedPlan.mixIn(base);
 323 
 324         boolean addEmbedded = false;
 325 
 326         List<ActionPlan> result = new ArrayList<ActionPlan>();
 327         for (BenchmarkListEntry br : benchmarks) {
 328             BenchmarkParams params = newBenchmarkParams(br, ActionMode.UNDEF);
 329 
 330             if (params.getForks() <= 0) {
 331                 if (options.getWarmupMode().orElse(Defaults.WARMUP_MODE).isIndi()) {
 332                     embeddedPlan.add(newAction(br, ActionMode.WARMUP_MEASUREMENT));
 333                 } else {
 334                     embeddedPlan.add(newAction(br, ActionMode.MEASUREMENT));
 335                 }
 336                 addEmbedded = true;
 337             }
 338 
 339             if (params.getForks() > 0) {
 340                 ActionPlan r = new ActionPlan(ActionType.FORKED);
 341                 r.mixIn(base);
 342                 if (options.getWarmupMode().orElse(Defaults.WARMUP_MODE).isIndi()) {
 343                     r.add(newAction(br, ActionMode.WARMUP_MEASUREMENT));
 344                 } else {
 345                     r.add(newAction(br, ActionMode.MEASUREMENT));
 346                 }
 347                 result.add(r);
 348             }
 349         }
 350 
 351         if (addEmbedded) {
 352             result.add(embeddedPlan);
 353         }
 354 
 355         return result;
 356     }
 357 
 358     private Action newAction(BenchmarkListEntry br, ActionMode mode) {
 359         return new Action(newBenchmarkParams(br, mode), mode);
 360     }
 361 
 362     private BenchmarkParams newBenchmarkParams(BenchmarkListEntry benchmark, ActionMode mode) {
 363         int[] threadGroups = options.getThreadGroups().orElse(benchmark.getThreadGroups());
 364 
 365         int threads = options.getThreads().orElse(
 366                 benchmark.getThreads().orElse(
 367                         Defaults.THREADS));
 368 
 369         if (threads == Threads.MAX) {
 370             if (cpuCount == 0) {
 371                 out.print("# Detecting actual CPU count: ");
 372                 cpuCount = Utils.figureOutHotCPUs();
 373                 out.println(cpuCount + " detected");
 374             }
 375             threads = cpuCount;
 376         }
 377 
 378         threads = Utils.roundUp(threads, Utils.sum(threadGroups));
 379 
 380         boolean synchIterations = (benchmark.getMode() != Mode.SingleShotTime) &&
 381                 options.shouldSyncIterations().orElse(Defaults.SYNC_ITERATIONS);
 382 
 383         IterationParams measurement = mode.doMeasurement() ?
 384                 new IterationParams(
 385                         IterationType.MEASUREMENT,
 386                         options.getMeasurementIterations().orElse(
 387                                 benchmark.getMeasurementIterations().orElse(
 388                                         (benchmark.getMode() == Mode.SingleShotTime) ? Defaults.MEASUREMENT_ITERATIONS_SINGLESHOT : Defaults.MEASUREMENT_ITERATIONS
 389                                 )),
 390                         options.getMeasurementTime().orElse(
 391                                 benchmark.getMeasurementTime().orElse(
 392                                         (benchmark.getMode() == Mode.SingleShotTime) ? TimeValue.NONE : Defaults.MEASUREMENT_TIME
 393                                 )),
 394                         options.getMeasurementBatchSize().orElse(
 395                                 benchmark.getMeasurementBatchSize().orElse(
 396                                         Defaults.MEASUREMENT_BATCHSIZE
 397                                 )
 398                         )
 399                 ) :
 400                 new IterationParams(IterationType.MEASUREMENT, 0, TimeValue.NONE, 1);
 401 
 402         IterationParams warmup = mode.doWarmup() ?
 403                 new IterationParams(
 404                         IterationType.WARMUP,
 405                         options.getWarmupIterations().orElse(
 406                                 benchmark.getWarmupIterations().orElse(
 407                                         (benchmark.getMode() == Mode.SingleShotTime) ? Defaults.WARMUP_ITERATIONS_SINGLESHOT : Defaults.WARMUP_ITERATIONS
 408                                 )),
 409                         options.getWarmupTime().orElse(
 410                                 benchmark.getWarmupTime().orElse(
 411                                         (benchmark.getMode() == Mode.SingleShotTime) ? TimeValue.NONE : Defaults.WARMUP_TIME
 412                                 )),
 413                         options.getWarmupBatchSize().orElse(
 414                                 benchmark.getWarmupBatchSize().orElse(
 415                                         Defaults.WARMUP_BATCHSIZE
 416                                 )
 417                         )
 418                 ) :
 419                 new IterationParams(IterationType.WARMUP, 0, TimeValue.NONE, 1);
 420 
 421         int forks = options.getForkCount().orElse(
 422                 benchmark.getForks().orElse(
 423                         Defaults.MEASUREMENT_FORKS));
 424 
 425         int warmupForks = options.getWarmupForkCount().orElse(
 426                 benchmark.getWarmupForks().orElse(
 427                         Defaults.WARMUP_FORKS));
 428 
 429         TimeUnit timeUnit = options.getTimeUnit().orElse(
 430                 benchmark.getTimeUnit().orElse(
 431                         Defaults.OUTPUT_TIMEUNIT));
 432 
 433         int opsPerInvocation = options.getOperationsPerInvocation().orElse(
 434                 benchmark.getOperationsPerInvocation().orElse(
 435                         Defaults.OPS_PER_INVOCATION));
 436 
 437         String jvm = options.getJvm().orElse(
 438                 benchmark.getJvm().orElse(Utils.getCurrentJvm()));
 439 
 440         Collection<String> jvmArgs = new ArrayList<String>();
 441 
 442         jvmArgs.addAll(options.getJvmArgsPrepend().orElse(
 443                 benchmark.getJvmArgsPrepend().orElse(Collections.<String>emptyList())));
 444 
 445         jvmArgs.addAll(options.getJvmArgs().orElse(
 446                 benchmark.getJvmArgs().orElse(ManagementFactory.getRuntimeMXBean().getInputArguments())));
 447 
 448         jvmArgs.addAll(options.getJvmArgsAppend().orElse(
 449                 benchmark.getJvmArgsAppend().orElse(Collections.<String>emptyList())));
 450 
 451         TimeValue timeout = options.getTimeout().orElse(
 452                 benchmark.getTimeout().orElse(Defaults.TIMEOUT));
 453 
 454         return new BenchmarkParams(benchmark.getUsername(), benchmark.generatedTarget(), synchIterations,
 455                 threads, threadGroups, forks, warmupForks,
 456                 warmup, measurement, benchmark.getMode(), benchmark.getWorkloadParams(), timeUnit, opsPerInvocation,
 457                 jvm, jvmArgs, timeout);
 458     }
 459 
 460     private List<WorkloadParams> explodeAllParams(BenchmarkListEntry br) throws RunnerException {
 461         Map<String, String[]> benchParams = br.getParams().orElse(Collections.<String, String[]>emptyMap());
 462         List<WorkloadParams> ps = new ArrayList<WorkloadParams>();
 463         for (Map.Entry<String, String[]> e : benchParams.entrySet()) {
 464             String k = e.getKey();
 465             String[] vals = e.getValue();
 466             Collection<String> values = options.getParameter(k).orElse(Arrays.asList(vals));
 467             if (values.isEmpty()) {
 468                 throw new RunnerException("Benchmark \"" + br.getUsername() +
 469                         "\" defines the parameter \"" + k + "\", but no default values.\n" +
 470                         "Define the default values within the annotation, or provide the parameter values at runtime.");
 471             }
 472             if (ps.isEmpty()) {
 473                 int idx = 0;
 474                 for (String v : values) {
 475                     WorkloadParams al = new WorkloadParams();
 476                     al.put(k, v, idx);
 477                     ps.add(al);
 478                     idx++;
 479                 }
 480             } else {
 481                 List<WorkloadParams> newPs = new ArrayList<WorkloadParams>();
 482                 for (WorkloadParams p : ps) {
 483                     int idx = 0;
 484                     for (String v : values) {
 485                         WorkloadParams al = p.copy();
 486                         al.put(k, v, idx);
 487                         newPs.add(al);
 488                         idx++;
 489                     }
 490                 }
 491                 ps = newPs;
 492             }
 493         }
 494         return ps;
 495     }
 496 
 497     private Collection<RunResult> runBenchmarks(SortedSet<BenchmarkListEntry> benchmarks) throws RunnerException {
 498         out.startRun();
 499 
 500         Multimap<BenchmarkParams, BenchmarkResult> results = new TreeMultimap<BenchmarkParams, BenchmarkResult>();
 501         List<ActionPlan> plan = getActionPlans(benchmarks);
 502 
 503         etaBeforeBenchmarks(plan);
 504 
 505         try {
 506             for (ActionPlan r : plan) {
 507                 Multimap<BenchmarkParams, BenchmarkResult> res;
 508                 switch (r.getType()) {
 509                     case EMBEDDED:
 510                         res = runBenchmarksEmbedded(r);
 511                         break;
 512                     case FORKED:
 513                         res = runSeparate(r);
 514                         break;
 515                     default:
 516                         throw new IllegalStateException("Unknown action plan type: " + r.getType());
 517                 }
 518 
 519                 for (BenchmarkParams br : res.keys()) {
 520                     results.putAll(br, res.get(br));
 521                 }
 522             }
 523 
 524             etaAfterBenchmarks();
 525 
 526             SortedSet<RunResult> runResults = mergeRunResults(results);
 527             out.endRun(runResults);
 528             return runResults;
 529         } catch (BenchmarkException be) {
 530             throw new RunnerException("Benchmark caught the exception", be.getCause());
 531         }
 532     }
 533 
 534     private SortedSet<RunResult> mergeRunResults(Multimap<BenchmarkParams, BenchmarkResult> results) {
 535         SortedSet<RunResult> result = new TreeSet<RunResult>(RunResult.DEFAULT_SORT_COMPARATOR);
 536         for (BenchmarkParams key : results.keys()) {
 537             result.add(new RunResult(key, results.get(key)));
 538         }
 539         return result;
 540     }
 541 
 542     private Multimap<BenchmarkParams, BenchmarkResult> runSeparate(ActionPlan actionPlan) {
 543         Multimap<BenchmarkParams, BenchmarkResult> results = new HashMultimap<BenchmarkParams, BenchmarkResult>();
 544 
 545         if (actionPlan.getMeasurementActions().size() != 1) {
 546             throw new IllegalStateException("Expect only single benchmark in the action plan, but was " + actionPlan.getMeasurementActions().size());
 547         }
 548 
 549         BinaryLinkServer server = null;
 550         try {
 551             server = new BinaryLinkServer(options, out);
 552 
 553             server.setPlan(actionPlan);
 554 
 555             BenchmarkParams params = actionPlan.getMeasurementActions().get(0).getParams();
 556 
 557             List<ExternalProfiler> profilers = ProfilerFactory.getSupportedExternal(options.getProfilers());
 558 
 559             boolean printOut = true;
 560             boolean printErr = true;
 561             List<String> javaInvokeOptions = new ArrayList<String>();
 562             List<String> javaOptions = new ArrayList<String>();
 563             for (ExternalProfiler prof : profilers) {
 564                 javaInvokeOptions.addAll(prof.addJVMInvokeOptions(params));
 565                 javaOptions.addAll(prof.addJVMOptions(params));
 566                 printOut &= prof.allowPrintOut();
 567                 printErr &= prof.allowPrintErr();
 568             }
 569 
 570             List<ExternalProfiler> profilersRev = new ArrayList<ExternalProfiler>(profilers);
 571             Collections.reverse(profilersRev);
 572 
 573             boolean forcePrint = options.verbosity().orElse(Defaults.VERBOSITY).equalsOrHigherThan(VerboseMode.EXTRA);
 574             printOut = forcePrint || printOut;
 575             printErr = forcePrint || printErr;
 576 
 577             List<String> forkedString  = getForkedMainCommand(params, javaInvokeOptions, javaOptions, server.getHost(), server.getPort());
 578             List<String> versionString = getVersionMainCommand(params);
 579 
 580             String opts = Utils.join(params.getJvmArgs(), " ");
 581             if (opts.trim().isEmpty()) {
 582                 opts = "<none>";
 583             }
 584 
 585             Version.printVersion(out);
 586             out.print("# VM version: " + Utils.join(Utils.runWith(versionString), "\n"));
 587             out.println("# VM invoker: " + params.getJvm());
 588             out.println("# VM options: " + opts);
 589             out.startBenchmark(params);
 590             out.println("");
 591 
 592             int forkCount = params.getForks();
 593             int warmupForkCount = params.getWarmupForks();
 594             if (warmupForkCount > 0) {
 595                 out.verbosePrintln("Warmup forking " + warmupForkCount + " times using command: " + forkedString);
 596                 for (int i = 0; i < warmupForkCount; i++) {
 597                     etaBeforeBenchmark();
 598                     out.println("# Warmup Fork: " + (i + 1) + " of " + warmupForkCount);
 599 
 600                     File stdErr = FileUtils.tempFile("stderr");
 601                     File stdOut = FileUtils.tempFile("stdout");
 602 
 603                     doFork(server, forkedString, stdOut, stdErr, printOut, printErr);
 604 
 605                     etaAfterBenchmark(params);
 606                     out.println("");
 607                 }
 608             }
 609 
 610             out.verbosePrintln("Forking " + forkCount + " times using command: " + forkedString);
 611             for (int i = 0; i < forkCount; i++) {
 612                 etaBeforeBenchmark();
 613                 out.println("# Fork: " + (i + 1) + " of " + forkCount);
 614 
 615                 File stdErr = FileUtils.tempFile("stderr");
 616                 File stdOut = FileUtils.tempFile("stdout");
 617 
 618                 if (!profilers.isEmpty()) {
 619                     out.print("# Preparing profilers: ");
 620                     for (ExternalProfiler profiler : profilers) {
 621                         out.print(profiler.getClass().getSimpleName() + " ");
 622                         profiler.beforeTrial(params);
 623                     }
 624                     out.println("");
 625 
 626                     List<String> consumed = new ArrayList<String>();
 627                     if (!printOut) consumed.add("stdout");
 628                     if (!printErr) consumed.add("stderr");
 629                     if (!consumed.isEmpty()) {
 630                         out.println("# Profilers consume " + Utils.join(consumed, " and ") + " from target VM, use -v " + VerboseMode.EXTRA + " to copy to console");
 631                     }
 632                 }
 633 
 634                 List<IterationResult> result = doFork(server, forkedString, stdOut, stdErr, printOut, printErr);
 635                 if (!result.isEmpty()) {
 636                     long pid = server.getClientPid();
 637 
 638                     BenchmarkResultMetaData md = server.getMetadata();
 639                     BenchmarkResult br = new BenchmarkResult(params, result, md);
 640 
 641                     if (!profilersRev.isEmpty()) {
 642                         out.print("# Processing profiler results: ");
 643                         for (ExternalProfiler profiler : profilersRev) {
 644                             out.print(profiler.getClass().getSimpleName() + " ");
 645                             for (Result profR : profiler.afterTrial(br, pid, stdOut, stdErr)) {
 646                                 br.addBenchmarkResult(profR);
 647                             }
 648                         }
 649                         out.println("");
 650                     }
 651 
 652                     results.put(params, br);
 653                 }
 654 
 655                 etaAfterBenchmark(params);
 656                 out.println("");
 657             }
 658 
 659             out.endBenchmark(new RunResult(params, results.get(params)).getAggregatedResult());
 660 
 661         } catch (IOException e) {
 662             results.clear();
 663             throw new BenchmarkException(e);
 664         } catch (BenchmarkException e) {
 665             results.clear();
 666             if (options.shouldFailOnError().orElse(Defaults.FAIL_ON_ERROR)) {
 667                 out.println("Benchmark had encountered error, and fail on error was requested");
 668                 throw e;
 669             }
 670         } finally {
 671             if (server != null) {
 672                 server.terminate();
 673             }
 674         }
 675 
 676         return results;
 677     }
 678 
 679     private List<IterationResult> doFork(BinaryLinkServer reader, List<String> commandString,
 680                                                               File stdOut, File stdErr, boolean printOut, boolean printErr) {
 681         FileOutputStream fosErr = null;
 682         FileOutputStream fosOut = null;
 683         try {
 684             ProcessBuilder pb = new ProcessBuilder(commandString);
 685             Process p = pb.start();
 686 
 687             fosErr = new FileOutputStream(stdErr);
 688             fosOut = new FileOutputStream(stdOut);
 689 
 690             // drain streams, else we might lock up
 691             InputStreamDrainer errDrainer = new InputStreamDrainer(p.getErrorStream(), fosErr);
 692             InputStreamDrainer outDrainer = new InputStreamDrainer(p.getInputStream(), fosOut);
 693 
 694             if (printErr) {
 695                 errDrainer.addOutputStream(new OutputFormatAdapter(out));
 696             }
 697 
 698             if (printOut) {
 699                 outDrainer.addOutputStream(new OutputFormatAdapter(out));
 700             }
 701 
 702             errDrainer.start();
 703             outDrainer.start();
 704 
 705             int ecode = p.waitFor();
 706 
 707             errDrainer.join();
 708             outDrainer.join();
 709 
 710             // need to wait for all pending messages to be processed
 711             // before starting the next benchmark
 712             reader.waitFinish();
 713 
 714             if (ecode != 0) {
 715                 out.println("<forked VM failed with exit code " + ecode + ">");
 716                 out.println("<stdout last='" + TAIL_LINES_ON_ERROR + " lines'>");
 717                 for (String l : FileUtils.tail(stdOut, TAIL_LINES_ON_ERROR)) {
 718                     out.println(l);
 719                 }
 720                 out.println("</stdout>");
 721                 out.println("<stderr last='" + TAIL_LINES_ON_ERROR + " lines'>");
 722                 for (String l : FileUtils.tail(stdErr, TAIL_LINES_ON_ERROR)) {
 723                     out.println(l);
 724                 }
 725                 out.println("</stderr>");
 726 
 727                 out.println("");
 728             }
 729 
 730             BenchmarkException exception = reader.getException();
 731             if (exception == null) {
 732                 if (ecode == 0) {
 733                     return reader.getResults();
 734                 } else {
 735                     throw new BenchmarkException(new IllegalStateException("Forked VM failed with exit code " + ecode));
 736                 }
 737             } else {
 738                 throw exception;
 739             }
 740 
 741         } catch (IOException ex) {
 742             out.println("<failed to invoke the VM, caught IOException: " + ex.getMessage() + ">");
 743             out.println("");
 744             throw new BenchmarkException(ex);
 745         } catch (InterruptedException ex) {
 746             out.println("<host VM has been interrupted waiting for forked VM: " + ex.getMessage() + ">");
 747             out.println("");
 748             throw new BenchmarkException(ex);
 749         } finally {
 750             FileUtils.safelyClose(fosErr);
 751             FileUtils.safelyClose(fosOut);
 752         }
 753     }
 754 
 755     /**
 756      * @param host host VM host
 757      * @param port host VM port
 758      * @return
 759      */
 760     List<String> getForkedMainCommand(BenchmarkParams benchmark, List<String> javaInvokeOptions, List<String> javaOptions, String host, int port) {
 761         List<String> command = new ArrayList<String>();
 762 
 763         // prefix java invoke options, if any profiler wants it
 764         command.addAll(javaInvokeOptions);
 765 
 766         // use supplied jvm, if given
 767         command.add(benchmark.getJvm());
 768 
 769         // use supplied jvm args, if given
 770         command.addAll(benchmark.getJvmArgs());
 771 
 772         // add profiler JVM commands, if any profiler wants it
 773         command.addAll(javaOptions);
 774 
 775         // add any compiler oracle hints
 776         CompilerHints.addCompilerHints(command);
 777 
 778         // assemble final process command
 779         command.add("-cp");
 780         if (Utils.isWindows()) {
 781             command.add('"' + System.getProperty("java.class.path") + '"');
 782         } else {
 783             command.add(System.getProperty("java.class.path"));
 784         }
 785 
 786         command.add(ForkedMain.class.getName());
 787 
 788         // Forked VM assumes the exact order of arguments:
 789         //   1) host name to back-connect
 790         //   2) host port to back-connect
 791         command.add(host);
 792         command.add(String.valueOf(port));
 793 
 794         return command;
 795     }
 796 
 797     /**
 798      * @return
 799      */
 800     List<String> getVersionMainCommand(BenchmarkParams benchmark) {
 801         List<String> command = new ArrayList<String>();
 802 
 803         // use supplied jvm, if given
 804         command.add(benchmark.getJvm());
 805 
 806         // assemble final process command
 807         command.add("-cp");
 808         if (Utils.isWindows()) {
 809             command.add('"' + System.getProperty("java.class.path") + '"');
 810         } else {
 811             command.add(System.getProperty("java.class.path"));
 812         }
 813 
 814         command.add(VersionMain.class.getName());
 815 
 816         return command;
 817     }
 818 
 819 }