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