blob: 57715c96b13a0ff01664556da58bcc03462958e4 [file] [log] [blame]
/*
* Copyright (C) 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.caliper;
import com.google.caliper.UserException.DisplayUsageException;
import com.google.caliper.UserException.ExceptionFromUserCodeException;
import com.google.caliper.util.InterleavedReader;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ObjectArrays;
import com.google.common.io.Closeables;
import com.google.gson.JsonObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;
import java.util.regex.Pattern;
/**
* Creates, executes and reports benchmark runs.
*/
public final class Runner {
private static final FileFilter UPLOAD_FILE_FILTER = new FileFilter() {
@Override public boolean accept(File file) {
return file.getName().endsWith(".xml") || file.getName().endsWith(".json");
}
};
private static final String FILE_NAME_DATE_FORMAT = "yyyy-MM-dd'T'HH-mm-ssZ";
private static final Splitter ARGUMENT_SPLITTER
= Splitter.on(Pattern.compile("\\s+")).omitEmptyStrings();
/** Command line arguments to the process */
private Arguments arguments;
private ScenarioSelection scenarioSelection;
private String createFileName(Result result) {
String timestamp = createTimestamp();
return String.format("%s.%s.json", result.getRun().getBenchmarkName(), timestamp);
}
private String createTimestamp() {
SimpleDateFormat dateFormat = new SimpleDateFormat(FILE_NAME_DATE_FORMAT, Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
dateFormat.setLenient(true);
return dateFormat.format(new Date());
}
public void run(String... args) {
this.arguments = Arguments.parse(args);
File resultsUploadFile = arguments.getUploadResultsFile();
if (resultsUploadFile != null) {
uploadResultsFileOrDir(resultsUploadFile);
return;
}
this.scenarioSelection = new ScenarioSelection(arguments);
if (arguments.getDebug()) {
debug();
return;
}
Result result = runOutOfProcess();
new ConsoleReport(result.getRun(), arguments).displayResults();
boolean saveResultsLocally = arguments.getSaveResultsFile() != null;
try {
postResults(result);
} catch (Exception e) {
System.out.println();
System.out.println(e);
saveResultsLocally = true;
}
if (saveResultsLocally) {
saveResults(result);
}
}
void uploadResultsFileOrDir(File resultsFileOrDir) {
try {
if (resultsFileOrDir.isDirectory()) {
for (File resultsFile : resultsFileOrDir.listFiles(UPLOAD_FILE_FILTER)) {
uploadResults(resultsFile);
}
} else {
uploadResults(resultsFileOrDir);
}
} catch (Exception e) {
throw new RuntimeException("uploading XML file failed", e);
}
}
private void uploadResults(File resultsUploadFile) throws IOException {
System.out.println();
System.out.println("Uploading " + resultsUploadFile.getCanonicalPath());
InputStream inputStream = new FileInputStream(resultsUploadFile);
try {
Result result = new ResultsReader().getResult(inputStream);
postResults(result);
} finally {
inputStream.close();
}
}
private void saveResults(Result result) {
File resultsFile = arguments.getSaveResultsFile();
File destinationFile;
if (resultsFile == null) {
File dir = new File("./caliper-results");
dir.mkdirs();
destinationFile = new File(dir, createFileName(result));
} else if (resultsFile.exists() && resultsFile.isDirectory()) {
destinationFile = new File(resultsFile, createFileName(result));
} else {
// assume this is a file
File parent = resultsFile.getParentFile();
if (parent != null) {
parent.mkdirs();
}
destinationFile = resultsFile;
}
PrintStream filePrintStream;
try {
filePrintStream = new PrintStream(new FileOutputStream(destinationFile));
} catch (FileNotFoundException e) {
throw new RuntimeException("can't open " + destinationFile, e);
}
String resultJson = Json.getGsonInstance().toJson(result);
try {
System.out.println();
System.out.println("Writing results to " + destinationFile.getCanonicalPath());
filePrintStream.print(resultJson);
} catch (Exception e) {
System.out.println(e);
System.out.println("Failed to write results to file, writing to standard out instead:");
System.out.println(resultJson);
System.out.flush();
} finally {
filePrintStream.close();
}
}
private void postResults(Result result) {
CaliperRc caliperrc = CaliperRc.INSTANCE;
String postUrl = caliperrc.getPostUrl();
String apiKey = caliperrc.getApiKey();
if (postUrl == null || apiKey == null) {
// TODO: probably nicer to show a message if only one is null
return;
}
try {
URL url = new URL(postUrl + apiKey + "/" + result.getRun().getBenchmarkName());
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(getProxy());
urlConnection.setDoOutput(true);
String resultJson = Json.getGsonInstance().toJson(result);
urlConnection.getOutputStream().write(resultJson.getBytes());
if (urlConnection.getResponseCode() == 200) {
System.out.println("");
System.out.println("View current and previous benchmark results online:");
BufferedReader in = new BufferedReader(
new InputStreamReader(urlConnection.getInputStream()));
System.out.println(" " + in.readLine());
in.close();
return;
}
System.out.println("Posting to " + postUrl + " failed: "
+ urlConnection.getResponseMessage());
BufferedReader reader = new BufferedReader(
new InputStreamReader(urlConnection.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (IOException e) {
throw new RuntimeException("Posting to " + postUrl + " failed.", e);
}
}
private Proxy getProxy() {
String proxyAddress = CaliperRc.INSTANCE.getProxy();
if (proxyAddress == null) {
return Proxy.NO_PROXY;
}
String[] proxyHostAndPort = proxyAddress.trim().split(":");
return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(
proxyHostAndPort[0], Integer.parseInt(proxyHostAndPort[1])));
}
private ScenarioResult runScenario(Scenario scenario) {
MeasurementResult timeMeasurementResult = measure(scenario, MeasurementType.TIME);
MeasurementSet allocationMeasurements = null;
String allocationEventLog = null;
MeasurementSet memoryMeasurements = null;
String memoryEventLog = null;
if (arguments.getMeasureMemory()) {
MeasurementResult allocationsMeasurementResult =
measure(scenario, MeasurementType.INSTANCE);
allocationMeasurements = allocationsMeasurementResult.getMeasurements();
allocationEventLog = allocationsMeasurementResult.getEventLog();
MeasurementResult memoryMeasurementResult =
measure(scenario, MeasurementType.MEMORY);
memoryMeasurements = memoryMeasurementResult.getMeasurements();
memoryEventLog = memoryMeasurementResult.getEventLog();
}
return new ScenarioResult(timeMeasurementResult.getMeasurements(),
timeMeasurementResult.getEventLog(),
allocationMeasurements, allocationEventLog,
memoryMeasurements, memoryEventLog);
}
private static class MeasurementResult {
private final MeasurementSet measurements;
private final String eventLog;
MeasurementResult(MeasurementSet measurements, String eventLog) {
this.measurements = measurements;
this.eventLog = eventLog;
}
public MeasurementSet getMeasurements() {
return measurements;
}
public String getEventLog() {
return eventLog;
}
}
private MeasurementResult measure(Scenario scenario, MeasurementType type) {
Vm vm = new VmFactory().createVm(scenario);
// this must be done before starting the forked process on certain VMs
ProcessBuilder processBuilder = createCommand(scenario, vm, type)
.redirectErrorStream(true);
Process timeProcess;
try {
timeProcess = processBuilder.start();
} catch (IOException e) {
throw new RuntimeException("failed to start subprocess", e);
}
MeasurementSet measurementSet = null;
StringBuilder eventLog = new StringBuilder();
InterleavedReader reader = null;
try {
reader = new InterleavedReader(arguments.getMarker(),
new InputStreamReader(timeProcess.getInputStream()));
Object o;
while ((o = reader.read()) != null) {
if (o instanceof String) {
eventLog.append(o);
} else if (measurementSet == null) {
JsonObject jsonObject = (JsonObject) o;
measurementSet = Json.measurementSetFromJson(jsonObject);
} else {
throw new RuntimeException("Unexpected value: " + o);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
Closeables.closeQuietly(reader);
timeProcess.destroy();
}
if (measurementSet == null) {
String message = "Failed to execute " + Joiner.on(" ").join(processBuilder.command());
System.err.println(" " + message);
System.err.println(eventLog.toString());
throw new ConfigurationException(message);
}
return new MeasurementResult(measurementSet, eventLog.toString());
}
private ProcessBuilder createCommand(Scenario scenario, Vm vm, MeasurementType type) {
File workingDirectory = new File(System.getProperty("user.dir"));
String classPath = System.getProperty("java.class.path");
if (classPath == null || classPath.length() == 0) {
throw new IllegalStateException("java.class.path is undefined in " + System.getProperties());
}
ImmutableList.Builder<String> vmArgs = ImmutableList.builder();
vmArgs.addAll(ARGUMENT_SPLITTER.split(scenario.getVariables().get(Scenario.VM_KEY)));
if (type == MeasurementType.INSTANCE || type == MeasurementType.MEMORY) {
String allocationJarFile = System.getenv("ALLOCATION_JAR");
vmArgs.add("-javaagent:" + allocationJarFile);
}
vmArgs.addAll(vm.getVmSpecificOptions(type, arguments));
Map<String, String> vmParameters = scenario.getVariables(
scenarioSelection.getVmParameterNames());
for (String vmParameter : vmParameters.values()) {
vmArgs.addAll(ARGUMENT_SPLITTER.split(vmParameter));
}
ImmutableList.Builder<String> caliperArgs = ImmutableList.builder();
caliperArgs.add("--warmupMillis").add(Long.toString(arguments.getWarmupMillis()));
caliperArgs.add("--runMillis").add(Long.toString(arguments.getRunMillis()));
caliperArgs.add("--measurementType").add(type.toString());
caliperArgs.add("--marker").add(arguments.getMarker());
Map<String,String> userParameters = scenario.getVariables(
scenarioSelection.getUserParameterNames());
for (Entry<String, String> entry : userParameters.entrySet()) {
caliperArgs.add("-D" + entry.getKey() + "=" + entry.getValue());
}
caliperArgs.add(arguments.getSuiteClassName());
return vm.newProcessBuilder(workingDirectory, classPath,
vmArgs.build(), InProcessRunner.class.getName(), caliperArgs.build());
}
private void debug() {
try {
int debugReps = arguments.getDebugReps();
InProcessRunner runner = new InProcessRunner();
DebugMeasurer measurer = new DebugMeasurer(debugReps);
for (Scenario scenario : scenarioSelection.select()) {
System.out.println("running " + debugReps + " debug reps of " + scenario);
runner.run(scenarioSelection, scenario, measurer);
}
} catch (Exception e) {
throw new ExceptionFromUserCodeException(e);
}
}
private Result runOutOfProcess() {
Date executedDate = new Date();
ImmutableMap.Builder<Scenario, ScenarioResult> resultsBuilder = ImmutableMap.builder();
try {
List<Scenario> scenarios = scenarioSelection.select();
int i = 0;
for (Scenario scenario : scenarios) {
beforeMeasurement(i++, scenarios.size(), scenario);
ScenarioResult scenarioResult = runScenario(scenario);
afterMeasurement(arguments.getMeasureMemory(), scenarioResult);
resultsBuilder.put(scenario, scenarioResult);
}
System.out.println();
Environment environment = new EnvironmentGetter().getEnvironmentSnapshot();
return new Result(
new Run(resultsBuilder.build(), arguments.getSuiteClassName(), executedDate),
environment);
} catch (Exception e) {
throw new ExceptionFromUserCodeException(e);
}
}
private void beforeMeasurement(int index, int total, Scenario scenario) {
double percentDone = (double) index / total;
System.out.printf("%2.0f%% %s", percentDone * 100, scenario);
}
private void afterMeasurement(boolean memoryMeasured, ScenarioResult scenarioResult) {
String memoryMeasurements = "";
if (memoryMeasured) {
MeasurementSet instanceMeasurementSet =
scenarioResult.getMeasurementSet(MeasurementType.INSTANCE);
String instanceUnit =
ConsoleReport.UNIT_ORDERING.min(instanceMeasurementSet.getUnitNames().entrySet()).getKey();
MeasurementSet memoryMeasurementSet = scenarioResult.getMeasurementSet(MeasurementType.MEMORY);
String memoryUnit =
ConsoleReport.UNIT_ORDERING.min(memoryMeasurementSet.getUnitNames().entrySet()).getKey();
memoryMeasurements = String.format(", allocated %s%s for a total of %s%s",
Math.round(instanceMeasurementSet.medianUnits()), instanceUnit,
Math.round(memoryMeasurementSet.medianUnits()), memoryUnit);
}
MeasurementSet timeMeasurementSet = scenarioResult.getMeasurementSet(MeasurementType.TIME);
String unit =
ConsoleReport.UNIT_ORDERING.min(timeMeasurementSet.getUnitNames().entrySet()).getKey();
System.out.printf(" %.2f %s; \u03C3=%.2f %s @ %d trials%s%n", timeMeasurementSet.medianUnits(),
unit, timeMeasurementSet.standardDeviationUnits(), unit,
timeMeasurementSet.getMeasurements().size(), memoryMeasurements);
}
public static void main(String[] args) {
try {
new Runner().run(args);
System.exit(0); // user code may have leave non-daemon threads behind!
} catch (DisplayUsageException e) {
e.display();
System.exit(0);
} catch (UserException e) {
e.display();
System.exit(1);
}
}
@SuppressWarnings("unchecked") // temporary fakery
public static void main(Class<? extends Benchmark> suite, String[] args) {
main(ObjectArrays.concat(args, suite.getName()));
}
}