package com.carrotsearch.junitbenchmarks;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import java.util.ArrayList;
import static com.carrotsearch.junitbenchmarks.BenchmarkOptionsSystemProperties.*;
/**
* Benchmark evaluator statement.
*/
final class BenchmarkStatement extends Statement
{
/**
* How many warmup runs should we execute for each test method?
*/
final static int DEFAULT_WARMUP_ROUNDS = 5;
/**
* How many actual benchmark runs should we execute for each test method?
*/
final static int DEFAULT_BENCHMARK_ROUNDS = 10;
/**
* If <code>true</code>, the local overrides using {@link BenchmarkOptions} are
* ignored and defaults (or globals passed via system properties) are used.
*/
private boolean ignoreAnnotationOptions = Boolean
.getBoolean(IGNORE_ANNOTATION_OPTIONS_PROPERTY);
/**
* Disable all forced garbage collector calls.
*/
private boolean ignoreCallGC = Boolean.getBoolean(IGNORE_CALLGC_PROPERTY);
private final Object target;
private final FrameworkMethod method;
private final Statement base;
private final BenchmarkOptions options;
private final IResultsConsumer [] consumers;
private long evaluationStart;
/* */
public BenchmarkStatement(Statement base, FrameworkMethod method, Object target,
IResultsConsumer... consumers)
{
this.base = base;
this.method = method;
this.target = target;
this.consumers = consumers;
this.options = resolveOptions(method);
}
/* Provide the default options from the annotation. */
@BenchmarkOptions
@SuppressWarnings("unused")
private void defaultOptions()
{
}
/**
* Reset start time for the current run. Use this to strip off setup overhead.
*/
public void reset()
{
evaluationStart = System.currentTimeMillis();
}
/* */
private BenchmarkOptions resolveOptions(FrameworkMethod method)
{
// Method-level override.
BenchmarkOptions options = method.getAnnotation(BenchmarkOptions.class);
if (options != null) return options;
// Class-level override. Look for annotations in this and superclasses.
Class<?> clz = target.getClass();
while (clz != null)
{
options = clz.getAnnotation(BenchmarkOptions.class);
if (options != null) return options;
clz = clz.getSuperclass();
}
// Defaults.
try
{
return getClass().getDeclaredMethod("defaultOptions").getAnnotation(
BenchmarkOptions.class);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/* */
@Override
public void evaluate() throws Throwable
{
final int warmupRounds = getIntOption(options.warmupRounds(),
WARMUP_ROUNDS_PROPERTY, DEFAULT_WARMUP_ROUNDS);
final int benchmarkRounds = getIntOption(options.benchmarkRounds(),
BENCHMARK_ROUNDS_PROPERTY, DEFAULT_BENCHMARK_ROUNDS);
final int totalRounds = warmupRounds + benchmarkRounds;
final ArrayList<SingleResult> results = new ArrayList<SingleResult>(totalRounds);
GCSnapshot gcSnapshot = null;
long warmupTime = System.currentTimeMillis();
long benchmarkTime = 0;
for (int i = 0; i < totalRounds; i++)
{
// We assume no reordering will take place here.
final long startTime = System.currentTimeMillis();
cleanupMemory();
final long gcTime = startTime - System.currentTimeMillis();
if (i == warmupRounds)
{
gcSnapshot = new GCSnapshot();
benchmarkTime = System.currentTimeMillis();
warmupTime = benchmarkTime - warmupTime;
}
evaluationStart = System.currentTimeMillis();
base.evaluate();
final long evaluationTime = System.currentTimeMillis() - evaluationStart;
results.add(new SingleResult(gcTime, evaluationTime));
}
benchmarkTime = System.currentTimeMillis() - benchmarkTime;
final Statistics stats = Statistics.from(
results.subList(warmupRounds, totalRounds));
final Result result = new Result(
target, method,
benchmarkRounds,
warmupRounds,
warmupTime,
benchmarkTime,
stats.evaluation,
stats.gc,
gcSnapshot,
options.perfUnits()
);
for (IResultsConsumer consumer : consumers)
consumer.accept(result);
}
/**
* Best effort attempt to clean up the memory if {@link BenchmarkOptions#callgc()} is
* enabled.
*/
private void cleanupMemory()
{
if (ignoreCallGC) return;
if (!options.callgc()) return;
/*
* Best-effort GC invocation. I really don't know of any other way to ensure a GC
* pass.
*/
System.gc();
System.gc();
Thread.yield();
}
/**
* Get an integer override from system properties.
*/
private int getIntOption(int localValue, String property, int defaultValue)
{
final String v = System.getProperty(property);
if (v != null && v.trim().length() > 0)
{
defaultValue = Integer.parseInt(v);
}
if (ignoreAnnotationOptions || localValue < 0)
{
return defaultValue;
}
return localValue;
}
}