package org.openjdk.jmh.runner;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.infra.BenchmarkParams;
import org.openjdk.jmh.infra.IterationParams;
import org.openjdk.jmh.results.BenchmarkResult;
import org.openjdk.jmh.results.BenchmarkResultMetaData;
import org.openjdk.jmh.results.IterationResult;
import org.openjdk.jmh.runner.format.OutputFormat;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.util.Multimap;
import org.openjdk.jmh.util.TreeMultimap;
import org.openjdk.jmh.util.Utils;
import org.openjdk.jmh.util.Version;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
abstract class BaseRunner {
private long projectedTotalTime;
private long projectedRunningTime;
private long actualRunningTime;
private long benchmarkStart;
protected final Options options;
protected final OutputFormat out;
public BaseRunner(Options options, OutputFormat handler) {
if (options == null) {
throw new IllegalArgumentException("Options is null.");
}
if (handler == null) {
throw new IllegalArgumentException("Handler is null.");
}
this.options = options;
this.out = handler;
}
protected void runBenchmarksForked(ActionPlan actionPlan, IterationResultAcceptor acceptor) {
for (Action action : actionPlan.getActions()) {
BenchmarkParams params = action.getParams();
ActionMode mode = action.getMode();
doSingle(params, mode, acceptor);
}
}
protected Multimap<BenchmarkParams, BenchmarkResult> runBenchmarksEmbedded(ActionPlan actionPlan) {
Multimap<BenchmarkParams, BenchmarkResult> results = new TreeMultimap<>();
for (Action action : actionPlan.getActions()) {
BenchmarkParams params = action.getParams();
ActionMode mode = action.getMode();
long startTime = System.currentTimeMillis();
out.startBenchmark(params);
out.println("");
etaBeforeBenchmark();
out.println("# Fork: N/A, test runs in the host VM");
out.println("# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***");
out.println("# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***");
final List<IterationResult> res = new ArrayList<>();
final List<BenchmarkResultMetaData> mds = new ArrayList<>();
IterationResultAcceptor acceptor = new IterationResultAcceptor() {
@Override
public void accept(IterationResult iterationData) {
res.add(iterationData);
}
@Override
public void acceptMeta(BenchmarkResultMetaData md) {
mds.add(md);
}
};
doSingle(params, mode, acceptor);
if (!res.isEmpty()) {
BenchmarkResultMetaData md = mds.get(0);
if (md != null) {
md.adjustStart(startTime);
}
BenchmarkResult br = new BenchmarkResult(params, res, md);
results.put(params, br);
out.endBenchmark(br);
}
etaAfterBenchmark(params);
}
return results;
}
private void doSingle(BenchmarkParams params, ActionMode mode, IterationResultAcceptor acceptor) {
try {
switch (mode) {
case WARMUP: {
runBenchmark(params, null);
out.println("");
break;
}
case WARMUP_MEASUREMENT:
case MEASUREMENT: {
runBenchmark(params, acceptor);
break;
}
default:
throw new IllegalStateException("Unknown mode: " + mode);
}
} catch (BenchmarkException be) {
out.println("<failure>");
out.println("");
for (Throwable cause : be.getSuppressed()) {
out.println(Utils.throwableToString(cause));
}
out.println("");
if (options.shouldFailOnError().orElse(Defaults.FAIL_ON_ERROR)) {
throw be;
}
}
}
protected void etaAfterBenchmark(BenchmarkParams params) {
long current = System.nanoTime();
projectedRunningTime += estimateTimeSingleFork(params);
actualRunningTime += (current - benchmarkStart);
benchmarkStart = current;
}
protected void etaBeforeBenchmarks(Collection<ActionPlan> plans) {
projectedTotalTime = 0;
for (ActionPlan plan : plans) {
for (Action act : plan.getActions()) {
BenchmarkParams params = act.getParams();
projectedTotalTime += (Math.max(1, params.getForks()) + params.getWarmupForks()) * estimateTimeSingleFork(params);
}
}
}
private long estimateTimeSingleFork(BenchmarkParams params) {
IterationParams wp = params.getWarmup();
IterationParams mp = params.getMeasurement();
long estimatedTime;
if (params.getMode() == Mode.SingleShotTime) {
estimatedTime = (wp.getCount() + mp.getCount()) * TimeUnit.MILLISECONDS.toNanos(1);
} else {
estimatedTime =
(wp.getCount() * wp.getTime().convertTo(TimeUnit.NANOSECONDS) +
mp.getCount() * mp.getTime().convertTo(TimeUnit.NANOSECONDS));
}
return estimatedTime;
}
protected void etaBeforeBenchmark() {
if (benchmarkStart == 0) {
benchmarkStart = System.nanoTime();
}
long totalETA;
double partsDone = 1.0D * projectedRunningTime / projectedTotalTime;
if (partsDone != 0) {
totalETA = (long) (actualRunningTime * (1.0D / partsDone - 1));
} else {
totalETA = projectedTotalTime;
}
out.println(String.format("# Run progress: %.2f%% complete, ETA %s", partsDone * 100, formatDuration(totalETA)));
}
protected void etaAfterBenchmarks() {
out.println(String.format("# Run complete. Total time: %s", formatDuration(actualRunningTime)));
out.println("");
}
private String formatDuration(long nanos) {
long days = TimeUnit.NANOSECONDS.toDays(nanos);
nanos -= days * TimeUnit.DAYS.toNanos(1);
long hrs = TimeUnit.NANOSECONDS.toHours(nanos);
nanos -= hrs * TimeUnit.HOURS.toNanos(1);
long mins = TimeUnit.NANOSECONDS.toMinutes(nanos);
nanos -= mins * TimeUnit.MINUTES.toNanos(1);
long secs = TimeUnit.NANOSECONDS.toSeconds(nanos);
return String.format("%s%02d:%02d:%02d", (days > 0) ? days + " days, " : "", hrs, mins, secs);
}
void runBenchmark(BenchmarkParams benchParams, IterationResultAcceptor acceptor) {
BenchmarkHandler handler = null;
try {
handler = new BenchmarkHandler(out, options, benchParams);
runBenchmark(benchParams, handler, acceptor);
} catch (BenchmarkException be) {
throw be;
} catch (Throwable ex) {
throw new BenchmarkException(ex);
} finally {
if (handler != null) {
handler.shutdown();
}
}
}
protected void runBenchmark(BenchmarkParams benchParams, BenchmarkHandler handler, IterationResultAcceptor acceptor) {
long warmupTime = System.currentTimeMillis();
long allWarmup = 0;
long allMeasurement = 0;
IterationParams wp = benchParams.getWarmup();
for (int i = 1; i <= wp.getCount(); i++) {
if (runSystemGC()) {
out.verbosePrintln("System.gc() executed");
}
out.iteration(benchParams, wp, i);
boolean isLastIteration = (benchParams.getMeasurement().getCount() == 0);
IterationResult ir = handler.runIteration(benchParams, wp, isLastIteration);
out.iterationResult(benchParams, wp, i, ir);
allWarmup += ir.getMetadata().getAllOps();
}
long measurementTime = System.currentTimeMillis();
IterationParams mp = benchParams.getMeasurement();
for (int i = 1; i <= mp.getCount(); i++) {
if (runSystemGC()) {
out.verbosePrintln("System.gc() executed");
}
out.iteration(benchParams, mp, i);
boolean isLastIteration = (i == mp.getCount());
IterationResult ir = handler.runIteration(benchParams, mp, isLastIteration);
out.iterationResult(benchParams, mp, i, ir);
allMeasurement += ir.getMetadata().getAllOps();
if (acceptor != null) {
acceptor.accept(ir);
}
}
long stopTime = System.currentTimeMillis();
BenchmarkResultMetaData md = new BenchmarkResultMetaData(
warmupTime, measurementTime, stopTime,
allWarmup, allMeasurement);
if (acceptor != null) {
acceptor.acceptMeta(md);
}
}
public boolean runSystemGC() {
if (options.shouldDoGC().orElse(Defaults.DO_GC)) {
List<GarbageCollectorMXBean> enabledBeans = new ArrayList<>();
long beforeGcCount = 0;
for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
long count = bean.getCollectionCount();
if (count != -1) {
enabledBeans.add(bean);
}
}
for (GarbageCollectorMXBean bean : enabledBeans) {
beforeGcCount += bean.getCollectionCount();
}
System.runFinalization();
System.gc();
System.runFinalization();
System.gc();
final int MAX_WAIT_MSEC = 20 * 1000;
if (enabledBeans.isEmpty()) {
out.println("WARNING: MXBeans can not report GC info. System.gc() invoked, pessimistically waiting " + MAX_WAIT_MSEC + " msecs");
try {
TimeUnit.MILLISECONDS.sleep(MAX_WAIT_MSEC);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return true;
}
boolean gcHappened = false;
long start = System.nanoTime();
while (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) < MAX_WAIT_MSEC) {
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
long afterGcCount = 0;
for (GarbageCollectorMXBean bean : enabledBeans) {
afterGcCount += bean.getCollectionCount();
}
if (!gcHappened) {
if (afterGcCount - beforeGcCount >= 2) {
gcHappened = true;
}
} else {
if (afterGcCount == beforeGcCount) {
return true;
}
beforeGcCount = afterGcCount;
}
}
if (gcHappened) {
out.println("WARNING: System.gc() was invoked but unable to wait while GC stopped, is GC too asynchronous?");
} else {
out.println("WARNING: System.gc() was invoked but couldn't detect a GC occurring, is System.gc() disabled?");
}
return false;
}
return false;
}
}