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 }