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 }