aboutsummaryrefslogtreecommitdiff
path: root/src/com/google/caliper
diff options
context:
space:
mode:
authorJesse Wilson <jessewilson@google.com>2010-01-13 17:12:18 -0800
committerJesse Wilson <jessewilson@google.com>2010-01-13 17:12:18 -0800
commitf062bf49c71013ec19cb71218778299535aceaa8 (patch)
tree17acfe2a694f55828f64caaae4c25179e61525a8 /src/com/google/caliper
parent1440b36663f61ebde1952d91b4a1f4c1a27fcefa (diff)
downloadcaliper-f062bf49c71013ec19cb71218778299535aceaa8.tar.gz
Update Caliper to r71.
Diffstat (limited to 'src/com/google/caliper')
-rw-r--r--src/com/google/caliper/Arguments.java156
-rw-r--r--src/com/google/caliper/Benchmark.java1
-rw-r--r--src/com/google/caliper/Caliper.java65
-rw-r--r--src/com/google/caliper/ConfigurationException.java8
-rw-r--r--src/com/google/caliper/ConsoleReport.java176
-rw-r--r--src/com/google/caliper/ExecutionException.java28
-rw-r--r--src/com/google/caliper/InProcessRunner.java71
-rw-r--r--src/com/google/caliper/Param.java9
-rw-r--r--src/com/google/caliper/Parameter.java95
-rw-r--r--src/com/google/caliper/Result.java37
-rw-r--r--src/com/google/caliper/Run.java76
-rw-r--r--src/com/google/caliper/Runner.java363
-rw-r--r--src/com/google/caliper/Scenario.java81
-rw-r--r--src/com/google/caliper/ScenarioSelection.java208
-rw-r--r--src/com/google/caliper/SimpleBenchmark.java28
-rw-r--r--src/com/google/caliper/TypeConverter.java58
-rw-r--r--src/com/google/caliper/UserException.java141
-rw-r--r--src/com/google/caliper/Xml.java108
18 files changed, 1194 insertions, 515 deletions
diff --git a/src/com/google/caliper/Arguments.java b/src/com/google/caliper/Arguments.java
new file mode 100644
index 0000000..e5ff0f7
--- /dev/null
+++ b/src/com/google/caliper/Arguments.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2010 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.MalformedParameterException;
+import com.google.caliper.UserException.MultipleBenchmarkClassesException;
+import com.google.caliper.UserException.NoBenchmarkClassException;
+import com.google.caliper.UserException.UnrecognizedOptionException;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Parse command line arguments for the runner and in-process runner.
+ */
+public final class Arguments {
+ private String suiteClassName;
+
+ /** JVMs to run in the benchmark */
+ private final Set<String> userVms = new LinkedHashSet<String>();
+
+ /**
+ * Parameter values specified by the user on the command line. Parameters with
+ * no value in this multimap will get their values from the benchmark suite.
+ */
+ private final Multimap<String, String> userParameters = LinkedHashMultimap.create();
+
+ private long warmupMillis = 5000;
+ private long runMillis = 5000;
+
+ /** The URL to post benchmark results to. */
+ private String postHost = "http://microbenchmarks.appspot.com/run/";
+
+ public String getSuiteClassName() {
+ return suiteClassName;
+ }
+
+ public Set<String> getUserVms() {
+ return userVms;
+ }
+
+ public Multimap<String, String> getUserParameters() {
+ return userParameters;
+ }
+
+ public long getWarmupMillis() {
+ return warmupMillis;
+ }
+
+ public long getRunMillis() {
+ return runMillis;
+ }
+
+ public String getPostHost() {
+ return postHost;
+ }
+
+ public static Arguments parse(String[] argsArray) {
+ Arguments result = new Arguments();
+
+ Iterator<String> args = Iterators.forArray(argsArray);
+ while (args.hasNext()) {
+ String arg = args.next();
+
+ if ("--help".equals(arg)) {
+ throw new DisplayUsageException();
+ }
+
+ if ("--postHost".equals(arg)) {
+ result.postHost = args.next();
+
+ } else if (arg.startsWith("-D")) {
+ int equalsSign = arg.indexOf('=');
+ if (equalsSign == -1) {
+ throw new MalformedParameterException(arg);
+ }
+ String name = arg.substring(2, equalsSign);
+ String value = arg.substring(equalsSign + 1);
+ result.userParameters.put(name, value);
+
+ } else if ("--warmupMillis".equals(arg)) {
+ result.warmupMillis = Long.parseLong(args.next());
+
+ } else if ("--runMillis".equals(arg)) {
+ result.runMillis = Long.parseLong(args.next());
+
+ } else if ("--vm".equals(arg)) {
+ result.userVms.add(args.next());
+
+ } else if (arg.startsWith("-")) {
+ throw new UnrecognizedOptionException(arg);
+
+ } else {
+ if (result.suiteClassName != null) {
+ throw new MultipleBenchmarkClassesException(result.suiteClassName, arg);
+ }
+ result.suiteClassName = arg;
+ }
+ }
+
+ if (result.suiteClassName == null) {
+ throw new NoBenchmarkClassException();
+ }
+
+ return result;
+ }
+
+ public static void printUsage() {
+ Arguments defaults = new Arguments();
+
+ System.out.println();
+ System.out.println("Usage: Runner [OPTIONS...] <benchmark>");
+ System.out.println();
+ System.out.println(" <benchmark>: a benchmark class or suite");
+ System.out.println();
+ System.out.println("OPTIONS");
+ System.out.println();
+ System.out.println(" -D<param>=<value>: fix a benchmark parameter to a given value.");
+ System.out.println(" When multiple values for the same parameter are given (via");
+ System.out.println(" multiple --Dx=y args), all supplied values are used.");
+ System.out.println();
+ System.out.println(" --inProcess: run the benchmark in the same JVM rather than spawning");
+ System.out.println(" another with the same classpath. By default each benchmark is");
+ System.out.println(" run in a separate VM");
+ System.out.println();
+ System.out.println(" --postHost <host>: the URL to post benchmark results to, or \"none\"");
+ System.out.println(" to skip posting results to the web.");
+ System.out.println(" default value: " + defaults.postHost);
+ System.out.println();
+ System.out.println(" --warmupMillis <millis>: duration to warmup each benchmark");
+ System.out.println();
+ System.out.println(" --runMillis <millis>: duration to execute each benchmark");
+ System.out.println();
+ System.out.println(" --vm <vm>: executable to test benchmark on");
+
+ // adding new options? don't forget to update executeForked()
+ }
+}
diff --git a/src/com/google/caliper/Benchmark.java b/src/com/google/caliper/Benchmark.java
index 19426e6..b5d35f9 100644
--- a/src/com/google/caliper/Benchmark.java
+++ b/src/com/google/caliper/Benchmark.java
@@ -16,7 +16,6 @@
package com.google.caliper;
-import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
diff --git a/src/com/google/caliper/Caliper.java b/src/com/google/caliper/Caliper.java
index 315431f..6c9d625 100644
--- a/src/com/google/caliper/Caliper.java
+++ b/src/com/google/caliper/Caliper.java
@@ -26,7 +26,7 @@ class Caliper {
private final long warmupNanos;
private final long runNanos;
- public Caliper(long warmupMillis, long runMillis) {
+ Caliper(long warmupMillis, long runMillis) {
checkArgument(warmupMillis > 50);
checkArgument(runMillis > 50);
@@ -37,15 +37,24 @@ class Caliper {
public double warmUp(TimedRunnable timedRunnable) throws Exception {
long startNanos = System.nanoTime();
long endNanos = startNanos + warmupNanos;
- int trials = 0;
long currentNanos;
+ int netReps = 0;
+ int reps = 1;
+
+ /*
+ * Run progressively more reps at a time until we cross our warmup
+ * threshold. This way any just-in-time compiler will be comfortable running
+ * multiple iterations of our measurement method.
+ */
while ((currentNanos = System.nanoTime()) < endNanos) {
- timedRunnable.run(1);
- trials++;
+ timedRunnable.run(reps);
+ netReps += reps;
+ reps *= 2;
}
- double nanosPerExecution = (currentNanos - startNanos) / trials;
+
+ double nanosPerExecution = (currentNanos - startNanos) / (double) netReps;
if (nanosPerExecution > 1000000000 || nanosPerExecution < 2) {
- throw new ConfigurationException("Runtime out of range");
+ throw new ConfigurationException("Runtime " + nanosPerExecution + " out of range");
}
return nanosPerExecution;
}
@@ -54,15 +63,51 @@ class Caliper {
* In the run proper, we predict how extrapolate based on warmup how many
* runs we're going to need, and run them all in a single batch.
*/
- public double run(TimedRunnable test, double estimatedNanosPerTrial) throws Exception {
+ public double run(TimedRunnable test, double estimatedNanosPerTrial)
+ throws Exception {
+ @SuppressWarnings("NumericCastThatLosesPrecision")
int trials = (int) (runNanos / estimatedNanosPerTrial);
if (trials == 0) {
trials = 1;
}
+
+ double nanosPerTrial = measure(test, trials);
+
+ // if the runtime was in the expected range, return it. We're good.
+ if (isPlausible(estimatedNanosPerTrial, nanosPerTrial)) {
+ return nanosPerTrial;
+ }
+
+ // The runtime was outside of the expected range. Perhaps the VM is inlining
+ // things too aggressively? We'll run more rounds to confirm that the
+ // runtime scales with the number of trials.
+ double nanosPerTrial2 = measure(test, trials * 4);
+ if (isPlausible(nanosPerTrial, nanosPerTrial2)) {
+ return nanosPerTrial;
+ }
+
+ throw new ConfigurationException("Measurement error: "
+ + "runtime isn't proportional to the number of repetitions!");
+ }
+
+ /**
+ * Returns true if the given measurement is consistent with the expected
+ * measurement.
+ */
+ private boolean isPlausible(double expected, double measurement) {
+ double ratio = measurement / expected;
+ return ratio > 0.5 && ratio < 2.0;
+ }
+
+ private double measure(TimedRunnable test, int trials) throws Exception {
+ prepareForTest();
long startNanos = System.nanoTime();
test.run(trials);
- long endNanos = System.nanoTime();
- estimatedNanosPerTrial = (endNanos - startNanos) / trials;
- return estimatedNanosPerTrial;
+ return (System.nanoTime() - startNanos) / (double) trials;
+ }
+
+ private void prepareForTest() {
+ System.gc();
+ System.gc();
}
} \ No newline at end of file
diff --git a/src/com/google/caliper/ConfigurationException.java b/src/com/google/caliper/ConfigurationException.java
index 5ad7bde..c4a35ec 100644
--- a/src/com/google/caliper/ConfigurationException.java
+++ b/src/com/google/caliper/ConfigurationException.java
@@ -19,13 +19,15 @@ package com.google.caliper;
/**
* Thrown upon occurrence of a configuration error.
*/
-public final class ConfigurationException extends RuntimeException {
+final class ConfigurationException extends RuntimeException {
- public ConfigurationException(String s) {
+ ConfigurationException(String s) {
super(s);
}
- public ConfigurationException(Throwable cause) {
+ ConfigurationException(Throwable cause) {
super(cause);
}
+
+ private static final long serialVersionUID = 0;
}
diff --git a/src/com/google/caliper/ConsoleReport.java b/src/com/google/caliper/ConsoleReport.java
index b367ec4..e59ceb6 100644
--- a/src/com/google/caliper/ConsoleReport.java
+++ b/src/com/google/caliper/ConsoleReport.java
@@ -16,8 +16,11 @@
package com.google.caliper;
-import com.google.common.collect.*;
-
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Ordering;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -39,13 +42,11 @@ import java.util.Map;
final class ConsoleReport {
private static final int bargraphWidth = 30;
- private static final String vmKey = "vm";
- private final List<Parameter> parameters;
- private final Result result;
- private final List<Run> runs;
+ private final List<Variable> variables;
+ private final Run run;
+ private final List<Scenario> scenarios;
- private final double minValue;
private final double maxValue;
private final double logMaxValue;
private final int decimalDigits;
@@ -53,53 +54,51 @@ final class ConsoleReport {
private final String units;
private final int measurementColumnLength;
- public ConsoleReport(Result result) {
- this.result = result;
+ ConsoleReport(Run run) {
+ this.run = run;
- double minValue = Double.POSITIVE_INFINITY;
- double maxValue = 0;
+ double min = Double.POSITIVE_INFINITY;
+ double max = 0;
Multimap<String, String> nameToValues = LinkedHashMultimap.create();
- List<Parameter> parametersBuilder = new ArrayList<Parameter>();
- for (Map.Entry<Run, Double> entry : result.getMeasurements().entrySet()) {
- Run run = entry.getKey();
+ List<Variable> variablesBuilder = new ArrayList<Variable>();
+ for (Map.Entry<Scenario, Double> entry : run.getMeasurements().entrySet()) {
+ Scenario scenario = entry.getKey();
double d = entry.getValue();
- minValue = Math.min(minValue, d);
- maxValue = Math.max(maxValue, d);
+ min = Math.min(min, d);
+ max = Math.max(max, d);
- for (Map.Entry<String, String> parameter : run.getParameters().entrySet()) {
- String name = parameter.getKey();
- nameToValues.put(name, parameter.getValue());
+ for (Map.Entry<String, String> variable : scenario.getVariables().entrySet()) {
+ String name = variable.getKey();
+ nameToValues.put(name, variable.getValue());
}
-
- nameToValues.put(vmKey, run.getVm());
}
for (Map.Entry<String, Collection<String>> entry : nameToValues.asMap().entrySet()) {
- Parameter parameter = new Parameter(entry.getKey(), entry.getValue());
- parametersBuilder.add(parameter);
+ Variable variable = new Variable(entry.getKey(), entry.getValue());
+ variablesBuilder.add(variable);
}
/*
- * Figure out how much influence each parameter has on the measured value.
- * We sum the measurements taken with each value of each parameter. For
- * parameters that have influence on the measurement, the sums will differ
- * by value. If the parameter has little influence, the sums will be similar
+ * Figure out how much influence each variable has on the measured value.
+ * We sum the measurements taken with each value of each variable. For
+ * variable that have influence on the measurement, the sums will differ
+ * by value. If the variable has little influence, the sums will be similar
* to one another and close to the overall average. We take the standard
- * deviation across each parameters collection of sums. Higher standard
+ * deviation across each variable's collection of sums. Higher standard
* deviation implies higher influence on the measured result.
*/
double sumOfAllMeasurements = 0;
- for (double measurement : result.getMeasurements().values()) {
+ for (double measurement : run.getMeasurements().values()) {
sumOfAllMeasurements += measurement;
}
- for (Parameter parameter : parametersBuilder) {
- int numValues = parameter.values.size();
+ for (Variable variable : variablesBuilder) {
+ int numValues = variable.values.size();
double[] sumForValue = new double[numValues];
- for (Map.Entry<Run, Double> entry : result.getMeasurements().entrySet()) {
- Run run = entry.getKey();
- sumForValue[parameter.index(run)] += entry.getValue();
+ for (Map.Entry<Scenario, Double> entry : run.getMeasurements().entrySet()) {
+ Scenario scenario = entry.getKey();
+ sumForValue[variable.index(scenario)] += entry.getValue();
}
double mean = sumOfAllMeasurements / sumForValue.length;
double stdDeviationSquared = 0;
@@ -107,16 +106,15 @@ final class ConsoleReport {
double distance = value - mean;
stdDeviationSquared += distance * distance;
}
- parameter.stdDeviation = Math.sqrt(stdDeviationSquared / numValues);
+ variable.stdDeviation = Math.sqrt(stdDeviationSquared / numValues);
}
- this.parameters = new StandardDeviationOrdering().reverse().sortedCopy(parametersBuilder);
- this.runs = new ByParametersOrdering().sortedCopy(result.getMeasurements().keySet());
- this.minValue = minValue;
- this.maxValue = maxValue;
- this.logMaxValue = Math.log(maxValue);
+ this.variables = new StandardDeviationOrdering().reverse().sortedCopy(variablesBuilder);
+ this.scenarios = new ByVariablesOrdering().sortedCopy(run.getMeasurements().keySet());
+ this.maxValue = max;
+ this.logMaxValue = Math.log(max);
- int numDigitsInMin = (int) Math.ceil(Math.log10(minValue));
+ int numDigitsInMin = ceil(Math.log10(min));
if (numDigitsInMin > 9) {
divideBy = 1000000000;
decimalDigits = Math.max(0, 9 + 3 - numDigitsInMin);
@@ -134,41 +132,37 @@ final class ConsoleReport {
decimalDigits = 0;
units = "ns";
}
- measurementColumnLength = maxValue > 0
- ? (int) Math.ceil(Math.log10(maxValue / divideBy)) + decimalDigits + 1
+ measurementColumnLength = max > 0
+ ? ceil(Math.log10(max / divideBy)) + decimalDigits + 1
: 1;
}
/**
- * A parameter plus all of its values.
+ * A variable and the set of values to which it has been assigned.
*/
- static class Parameter {
+ private static class Variable {
final String name;
final ImmutableList<String> values;
final int maxLength;
double stdDeviation;
- public Parameter(String name, Collection<String> values) {
+ Variable(String name, Collection<String> values) {
this.name = name;
this.values = ImmutableList.copyOf(values);
- int maxLength = name.length();
+ int maxLen = name.length();
for (String value : values) {
- maxLength = Math.max(maxLength, value.length());
+ maxLen = Math.max(maxLen, value.length());
}
- this.maxLength = maxLength;
+ this.maxLength = maxLen;
}
- String get(Run run) {
- if (vmKey.equals(name)) {
- return run.getVm();
- } else {
- return run.getParameters().get(name);
- }
+ String get(Scenario scenario) {
+ return scenario.getVariables().get(name);
}
- int index(Run run) {
- return values.indexOf(get(run));
+ int index(Scenario scenario) {
+ return values.indexOf(get(scenario));
}
boolean isInteresting() {
@@ -177,23 +171,23 @@ final class ConsoleReport {
}
/**
- * Orders the different parameters by their standard deviation. This results
+ * Orders the different variables by their standard deviation. This results
* in an appropriate grouping of output values.
*/
- static class StandardDeviationOrdering extends Ordering<Parameter> {
- public int compare(Parameter a, Parameter b) {
+ private static class StandardDeviationOrdering extends Ordering<Variable> {
+ public int compare(Variable a, Variable b) {
return Double.compare(a.stdDeviation, b.stdDeviation);
}
}
/**
- * Orders runs by the parameters.
+ * Orders scenarios by the variables.
*/
- class ByParametersOrdering extends Ordering<Run> {
- public int compare(Run a, Run b) {
- for (Parameter parameter : parameters) {
- int aValue = parameter.values.indexOf(parameter.get(a));
- int bValue = parameter.values.indexOf(parameter.get(b));
+ private class ByVariablesOrdering extends Ordering<Scenario> {
+ public int compare(Scenario a, Scenario b) {
+ for (Variable variable : variables) {
+ int aValue = variable.values.indexOf(variable.get(a));
+ int bValue = variable.values.indexOf(variable.get(b));
int diff = aValue - bValue;
if (diff != 0) {
return diff;
@@ -206,7 +200,7 @@ final class ConsoleReport {
void displayResults() {
printValues();
System.out.println();
- printUninterestingParameters();
+ printUninterestingVariables();
}
/**
@@ -214,33 +208,33 @@ final class ConsoleReport {
*/
private void printValues() {
// header
- for (Parameter parameter : parameters) {
- if (parameter.isInteresting()) {
- System.out.printf("%" + parameter.maxLength + "s ", parameter.name);
+ for (Variable variable : variables) {
+ if (variable.isInteresting()) {
+ System.out.printf("%" + variable.maxLength + "s ", variable.name);
}
}
System.out.printf("%" + measurementColumnLength + "s logarithmic runtime%n", units);
// rows
String numbersFormat = "%" + measurementColumnLength + "." + decimalDigits + "f %s%n";
- for (Run run : runs) {
- for (Parameter parameter : parameters) {
- if (parameter.isInteresting()) {
- System.out.printf("%" + parameter.maxLength + "s ", parameter.get(run));
+ for (Scenario scenario : scenarios) {
+ for (Variable variable : variables) {
+ if (variable.isInteresting()) {
+ System.out.printf("%" + variable.maxLength + "s ", variable.get(scenario));
}
}
- double measurement = result.getMeasurements().get(run);
+ double measurement = run.getMeasurements().get(scenario);
System.out.printf(numbersFormat, measurement / divideBy, bargraph(measurement));
}
}
/**
- * Prints parameters with only one unique value.
+ * Prints variables with only one unique value.
*/
- private void printUninterestingParameters() {
- for (Parameter parameter : parameters) {
- if (!parameter.isInteresting()) {
- System.out.println(parameter.name + ": " + Iterables.getOnlyElement(parameter.values));
+ private void printUninterestingVariables() {
+ for (Variable variable : variables) {
+ if (!variable.isInteresting()) {
+ System.out.println(variable.name + ": " + Iterables.getOnlyElement(variable.values));
}
}
}
@@ -250,17 +244,27 @@ final class ConsoleReport {
* value.
*/
private String bargraph(double value) {
- int numLinearChars = (int) ((value / maxValue) * bargraphWidth);
+ int numLinearChars = floor(value / maxValue * bargraphWidth);
double logValue = Math.log(value);
- int numChars = (int) ((logValue / logMaxValue) * bargraphWidth);
- StringBuilder result = new StringBuilder(numChars);
+ int numChars = floor(logValue / logMaxValue * bargraphWidth);
+ StringBuilder sb = new StringBuilder(numChars);
for (int i = 0; i < numLinearChars; i++) {
- result.append("X");
+ sb.append("X");
}
for (int i = numLinearChars; i < numChars; i++) {
- result.append("|");
+ sb.append("|");
}
- return result.toString();
+ return sb.toString();
+ }
+
+ @SuppressWarnings("NumericCastThatLosesPrecision")
+ private static int floor(double d) {
+ return (int) d;
+ }
+
+ @SuppressWarnings("NumericCastThatLosesPrecision")
+ private static int ceil(double d) {
+ return (int) Math.ceil(d);
}
}
diff --git a/src/com/google/caliper/ExecutionException.java b/src/com/google/caliper/ExecutionException.java
deleted file mode 100644
index 7d8a592..0000000
--- a/src/com/google/caliper/ExecutionException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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;
-
-/**
- * Thrown upon occurrence of a runtime failure during test construction or
- * execution.
- */
-public final class ExecutionException extends RuntimeException {
-
- public ExecutionException(Throwable throwable) {
- super(throwable);
- }
-}
diff --git a/src/com/google/caliper/InProcessRunner.java b/src/com/google/caliper/InProcessRunner.java
new file mode 100644
index 0000000..33e9e00
--- /dev/null
+++ b/src/com/google/caliper/InProcessRunner.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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.CantCustomizeInProcessVmException;
+import com.google.caliper.UserException.ExceptionFromUserCodeException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Executes a benchmark in the current VM.
+ */
+final class InProcessRunner {
+
+ public void run(String... args) {
+ Arguments arguments = Arguments.parse(args);
+
+ if (!arguments.getUserVms().isEmpty()) {
+ throw new CantCustomizeInProcessVmException();
+ }
+
+ ScenarioSelection scenarioSelection = new ScenarioSelection(arguments);
+
+ PrintStream resultStream = System.out;
+ System.setOut(nullPrintStream());
+ System.setErr(nullPrintStream());
+
+ try {
+ Caliper caliper = new Caliper(arguments.getWarmupMillis(), arguments.getRunMillis());
+
+ for (Scenario scenario : scenarioSelection.select()) {
+ TimedRunnable timedRunnable = scenarioSelection.createBenchmark(scenario);
+ double warmupNanosPerTrial = caliper.warmUp(timedRunnable);
+ double nanosPerTrial = caliper.run(timedRunnable, warmupNanosPerTrial);
+ resultStream.println(nanosPerTrial);
+ }
+ } catch (Exception e) {
+ throw new ExceptionFromUserCodeException(e);
+ }
+ }
+
+ public static void main(String... args) {
+ try {
+ new InProcessRunner().run(args);
+ } catch (UserException e) {
+ e.display(); // TODO: send this to the host process
+ System.exit(1);
+ }
+ }
+
+ public PrintStream nullPrintStream() {
+ return new PrintStream(new OutputStream() {
+ public void write(int b) throws IOException {}
+ });
+ }
+}
diff --git a/src/com/google/caliper/Param.java b/src/com/google/caliper/Param.java
index 28d3588..bea0269 100644
--- a/src/com/google/caliper/Param.java
+++ b/src/com/google/caliper/Param.java
@@ -26,4 +26,11 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
-public @interface Param {}
+public @interface Param {
+ /**
+ * One or more default values, as strings, that this parameter should be given if none are
+ * specified on the command line. If values are specified on the command line, the defaults given
+ * here are all ignored.
+ */
+ String[] value() default {};
+}
diff --git a/src/com/google/caliper/Parameter.java b/src/com/google/caliper/Parameter.java
index 1ba77b5..caca252 100644
--- a/src/com/google/caliper/Parameter.java
+++ b/src/com/google/caliper/Parameter.java
@@ -16,8 +16,19 @@
package com.google.caliper;
-import java.lang.reflect.*;
-import java.util.*;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
/**
* A parameter in a {@link SimpleBenchmark}.
@@ -35,22 +46,35 @@ abstract class Parameter<T> {
*/
public static Map<String, Parameter<?>> forClass(Class<? extends Benchmark> suiteClass) {
Map<String, Parameter<?>> parameters = new TreeMap<String, Parameter<?>>();
- for (final Field field : suiteClass.getDeclaredFields()) {
+ for (Field field : suiteClass.getDeclaredFields()) {
if (field.isAnnotationPresent(Param.class)) {
field.setAccessible(true);
- Parameter parameter = Parameter.forField(suiteClass, field);
+ Parameter<?> parameter = forField(suiteClass, field);
parameters.put(parameter.getName(), parameter);
}
}
return parameters;
}
- public static Parameter forField(
+ private static Parameter<?> forField(
Class<? extends Benchmark> suiteClass, final Field field) {
- Parameter result = null;
+ // First check for String values on the annotation itself
+ final Object[] defaults = field.getAnnotation(Param.class).value();
+ if (defaults.length > 0) {
+ return new Parameter<Object>(field) {
+ @Override public Collection<Object> values() throws Exception {
+ return Arrays.asList(defaults);
+ }
+ };
+ // TODO: or should we continue so we can give an error/warning if params are also give in a
+ // method or field?
+ }
+
+ Parameter<?> result = null;
Type returnType = null;
Member member = null;
+ // Now check for a fooValues() method
try {
final Method valuesMethod = suiteClass.getDeclaredMethod(field.getName() + "Values");
valuesMethod.setAccessible(true);
@@ -58,13 +82,14 @@ abstract class Parameter<T> {
returnType = valuesMethod.getGenericReturnType();
result = new Parameter<Object>(field) {
@SuppressWarnings("unchecked") // guarded below
- public Collection<Object> values() throws Exception {
+ @Override public Collection<Object> values() throws Exception {
return (Collection<Object>) valuesMethod.invoke(null);
}
};
} catch (NoSuchMethodException ignored) {
}
+ // Now check for a fooValues field
try {
final Field valuesField = suiteClass.getDeclaredField(field.getName() + "Values");
valuesField.setAccessible(true);
@@ -75,36 +100,58 @@ abstract class Parameter<T> {
returnType = valuesField.getGenericType();
result = new Parameter<Object>(field) {
@SuppressWarnings("unchecked") // guarded below
- public Collection<Object> values() throws Exception {
+ @Override public Collection<Object> values() throws Exception {
return (Collection<Object>) valuesField.get(null);
}
};
} catch (NoSuchFieldException ignored) {
}
- if (result == null) {
- throw new ConfigurationException("No values member defined for " + field);
+ if (member != null && !Modifier.isStatic(member.getModifiers())) {
+ throw new ConfigurationException("Values member must be static " + member);
+ }
+
+ // If there isn't a values member but the parameter is an enum, we default
+ // to EnumSet.allOf.
+ if (member == null && field.getType().isEnum()) {
+ returnType = Collection.class;
+ result = new Parameter<Object>(field) {
+ // TODO: figure out the simplest way to make this compile and be green in IDEA too
+ @SuppressWarnings({"unchecked", "RawUseOfParameterizedType", "RedundantCast"})
+ // guarded above
+ @Override public Collection<Object> values() throws Exception {
+ Set<Enum> set = EnumSet.allOf((Class<Enum>) field.getType());
+ return (Collection) set;
+ }
+ };
}
- if (!Modifier.isStatic(member.getModifiers())) {
- throw new ConfigurationException("Values member must be static " + member);
+ if (result == null) {
+ return new Parameter<Object>(field) {
+ @Override public Collection<Object> values() {
+ // TODO: need tests to make sure this fails properly when no cmdline params given and
+ // works properly when they are given
+ return Collections.emptySet();
+ }
+ };
+ } else if (!isValidReturnType(returnType)) {
+ throw new ConfigurationException("Invalid return type " + returnType
+ + " for values member " + member + "; must be Collection");
}
+ return result;
+ }
- // validate return type
- boolean valid = false;
+ private static boolean isValidReturnType(Type returnType) {
+ if (returnType == Collection.class) {
+ return true;
+ }
if (returnType instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) returnType;
if (type.getRawType() == Collection.class) {
- valid = true;
+ return true;
}
}
-
- if (!valid) {
- throw new ConfigurationException("Invalid return type " + returnType
- + " for values member " + member + "; must be Collection");
- }
-
- return result;
+ return false;
}
/**
@@ -129,7 +176,7 @@ abstract class Parameter<T> {
/**
* Returns the field's name.
*/
- public String getName() {
+ String getName() {
return field.getName();
}
-} \ No newline at end of file
+}
diff --git a/src/com/google/caliper/Result.java b/src/com/google/caliper/Result.java
deleted file mode 100644
index 888a9f4..0000000
--- a/src/com/google/caliper/Result.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * 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.common.collect.ImmutableMap;
-
-import java.util.Map;
-
-/**
- * The complete result of a benchmark suite run.
- */
-final class Result {
-
- private final ImmutableMap<Run, Double> measurements;
-
- public Result(Map<Run, Double> measurements) {
- this.measurements = ImmutableMap.copyOf(measurements);
- }
-
- public ImmutableMap<Run, Double> getMeasurements() {
- return measurements;
- }
-}
diff --git a/src/com/google/caliper/Run.java b/src/com/google/caliper/Run.java
index a9109de..f2e71de 100644
--- a/src/com/google/caliper/Run.java
+++ b/src/com/google/caliper/Run.java
@@ -1,4 +1,4 @@
-/*
+/**
* Copyright (C) 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,33 +16,77 @@
package com.google.caliper;
-import com.google.common.collect.ImmutableMap;
-
-import java.lang.reflect.Method;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.LinkedHashMap;
import java.util.Map;
/**
- * A configured benchmark.
+ * The complete result of a benchmark suite run.
+ *
+ * <p>Gwt-safe.
*/
-final class Run {
+public final class Run
+ implements Serializable /* for GWT Serialization */ {
+
+ private /*final*/ Map<Scenario, Double> measurements;
+ private /*final*/ String benchmarkName;
+ private /*final*/ String executedByUuid;
+ private /*final*/ long executedTimestamp;
- private final ImmutableMap<String, String> parameters;
- private final String vm;
+ // TODO: add more run properites such as checksums of the executed code
- public Run(Map<String, String> parameters, String vm) {
- this.parameters = ImmutableMap.copyOf(parameters);
- this.vm = vm;
+ public Run(Map<Scenario, Double> measurements,
+ String benchmarkName, String executedByUuid, Date executedTimestamp) {
+ if (benchmarkName == null || executedByUuid == null || executedTimestamp == null) {
+ throw new NullPointerException();
+ }
+
+ this.measurements = new LinkedHashMap<Scenario, Double>(measurements);
+ this.benchmarkName = benchmarkName;
+ this.executedByUuid = executedByUuid;
+ this.executedTimestamp = executedTimestamp.getTime();
+ }
+
+ public Map<Scenario, Double> getMeasurements() {
+ return measurements;
}
- public ImmutableMap<String, String> getParameters() {
- return parameters;
+ public String getBenchmarkName() {
+ return benchmarkName;
}
- public String getVm() {
- return vm;
+ public String getExecutedByUuid() {
+ return executedByUuid;
+ }
+
+ public Date getExecutedTimestamp() {
+ return new Date(executedTimestamp);
+ }
+
+ @Override public boolean equals(Object o) {
+ if (o instanceof Run) {
+ Run that = (Run) o;
+ return measurements.equals(that.measurements)
+ && benchmarkName.equals(that.benchmarkName)
+ && executedByUuid.equals(that.executedByUuid)
+ && executedTimestamp == that.executedTimestamp;
+ }
+
+ return false;
+ }
+
+ @Override public int hashCode() {
+ int result = measurements.hashCode();
+ result = result * 37 + benchmarkName.hashCode();
+ result = result * 37 + executedByUuid.hashCode();
+ result = result * 37 + (int) ((executedTimestamp >> 32) ^ executedTimestamp);
+ return result;
}
@Override public String toString() {
- return "Run" + parameters;
+ return measurements.toString();
}
+
+ private Run() {} // for GWT Serialization
}
diff --git a/src/com/google/caliper/Runner.java b/src/com/google/caliper/Runner.java
index 72442db..e359df8 100644
--- a/src/com/google/caliper/Runner.java
+++ b/src/com/google/caliper/Runner.java
@@ -16,180 +16,113 @@
package com.google.caliper;
+import com.google.caliper.UserException.ExceptionFromUserCodeException;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
-
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.ObjectArrays;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.UUID;
/**
* Creates, executes and reports benchmark runs.
*/
public final class Runner {
- private String suiteClassName;
- private Benchmark suite;
-
- /** Effective parameters to run in the benchmark. */
- private Multimap<String, String> parameters = LinkedHashMultimap.create();
-
- /** JVMs to run in the benchmark */
- private Set<String> userVms = new LinkedHashSet<String>();
-
- /**
- * Parameter values specified by the user on the command line. Parameters with
- * no value in this multimap will get their values from the benchmark suite.
- */
- private Multimap<String, String> userParameters = LinkedHashMultimap.create();
-
- /**
- * True if each benchmark should run in process.
- */
- private boolean inProcess;
-
- private long warmupMillis = 5000;
- private long runMillis = 5000;
+ /** Command line arguments to the process */
+ private Arguments arguments;
+ private ScenarioSelection scenarioSelection;
/**
- * Sets the named parameter to the specified value. This value will replace
- * the benchmark suite's default values for the parameter. Multiple calls to
- * this method will cause benchmarks for each value to be run.
+ * Returns the UUID of the executing host. Multiple runs by the same user on
+ * the same machine should yield the same result.
*/
- void setParameter(String name, String value) {
- userParameters.put(name, value);
- }
-
- private void prepareSuite() {
+ private String getExecutedByUuid() {
try {
- @SuppressWarnings("unchecked") // guarded by the if statement that follows
- Class<? extends Benchmark> suiteClass
- = (Class<? extends Benchmark>) Class.forName(suiteClassName);
- if (!Benchmark.class.isAssignableFrom(suiteClass)) {
- throw new ConfigurationException(suiteClass + " is not a benchmark suite.");
+ File dotCaliperRc = new File(System.getProperty("user.home"), ".caliperrc");
+ Properties properties = new Properties();
+ if (dotCaliperRc.exists()) {
+ properties.load(new FileInputStream(dotCaliperRc));
}
- Constructor<? extends Benchmark> constructor = suiteClass.getDeclaredConstructor();
- suite = constructor.newInstance();
- } catch (InvocationTargetException e) {
- throw new ExecutionException(e.getCause());
- } catch (Exception e) {
- throw new ConfigurationException(e);
- }
- }
-
- private void prepareParameters() {
- for (String key : suite.parameterNames()) {
- // first check if the user has specified values
- Collection<String> userValues = userParameters.get(key);
- if (!userValues.isEmpty()) {
- parameters.putAll(key, userValues);
- // TODO: type convert 'em to validate?
-
- } else { // otherwise use the default values from the suite
- Set<String> values = suite.parameterValues(key);
- if (values.isEmpty()) {
- throw new ConfigurationException(key + " has no values");
- }
- parameters.putAll(key, values);
+ String userUuid = properties.getProperty("userUuid");
+ if (userUuid == null) {
+ userUuid = UUID.randomUUID().toString();
+ properties.setProperty("userUuid", userUuid);
+ properties.store(new FileOutputStream(dotCaliperRc), "");
}
+
+ return userUuid;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
}
- private ImmutableSet<String> defaultVms() {
- return "Dalvik".equals(System.getProperty("java.vm.name"))
- ? ImmutableSet.of("dalvikvm")
- : ImmutableSet.of("java");
+ public void run(String... args) {
+ this.arguments = Arguments.parse(args);
+ this.scenarioSelection = new ScenarioSelection(arguments);
+ Run run = runOutOfProcess();
+ new ConsoleReport(run).displayResults();
+ postResults(run);
}
- /**
- * Returns a complete set of runs with every combination of values and
- * benchmark classes.
- */
- private List<Run> createRuns() throws Exception {
- List<RunBuilder> builders = new ArrayList<RunBuilder>();
-
- // create runs for each VMs
- Set<String> vms = userVms.isEmpty()
- ? defaultVms()
- : userVms;
- for (String vm : vms) {
- RunBuilder runBuilder = new RunBuilder();
- runBuilder.vm = vm;
- builders.add(runBuilder);
+ private void postResults(Run run) {
+ String postHost = arguments.getPostHost();
+ if ("none".equals(postHost)) {
+ return;
}
- for (Map.Entry<String, Collection<String>> parameter : parameters.asMap().entrySet()) {
- Iterator<String> values = parameter.getValue().iterator();
- if (!values.hasNext()) {
- throw new ConfigurationException("Not enough values for " + parameter);
- }
-
- String key = parameter.getKey();
-
- String firstValue = values.next();
- for (RunBuilder builder : builders) {
- builder.parameters.put(key, firstValue);
+ try {
+ URL url = new URL(postHost + run.getExecutedByUuid() + "/" + run.getBenchmarkName());
+ HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setDoOutput(true);
+ Xml.runToXml(run, urlConnection.getOutputStream());
+ if (urlConnection.getResponseCode() == 200) {
+ System.out.println("");
+ System.out.println("View current and previous benchmark results online:");
+ System.out.println(" " + url);
+ return;
}
- // multiply the size of the specs by the number of alternate values
- int size = builders.size();
- while (values.hasNext()) {
- String alternate = values.next();
- for (int s = 0; s < size; s++) {
- RunBuilder copy = builders.get(s).copy();
- copy.parameters.put(key, alternate);
- builders.add(copy);
- }
+ System.out.println("Posting to " + postHost + " failed: "
+ + urlConnection.getResponseMessage());
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(urlConnection.getInputStream()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ System.out.println(line);
}
- }
-
- List<Run> result = new ArrayList<Run>();
- for (RunBuilder builder : builders) {
- result.add(builder.build());
- }
-
- return result;
- }
-
- static class RunBuilder {
- Map<String, String> parameters = new LinkedHashMap<String, String>();
- String vm;
-
- RunBuilder copy() {
- RunBuilder result = new RunBuilder();
- result.parameters.putAll(parameters);
- result.vm = vm;
- return result;
- }
-
- public Run build() {
- return new Run(parameters, vm);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
}
- private double executeForked(Run run) {
+ private double executeForked(Scenario scenario) {
ProcessBuilder builder = new ProcessBuilder();
List<String> command = builder.command();
- command.addAll(Arrays.asList(run.getVm().split("\\s+")));
+ command.addAll(Arrays.asList(scenario.getVariables().get(Scenario.VM_KEY).split("\\s+")));
command.add("-cp");
command.add(System.getProperty("java.class.path"));
- command.add(Runner.class.getName());
+ command.add(InProcessRunner.class.getName());
command.add("--warmupMillis");
- command.add(String.valueOf(warmupMillis));
+ command.add(String.valueOf(arguments.getWarmupMillis()));
command.add("--runMillis");
- command.add(String.valueOf(runMillis));
- command.add("--inProcess");
- for (Map.Entry<String, String> entry : run.getParameters().entrySet()) {
+ command.add(String.valueOf(arguments.getRunMillis()));
+ for (Entry<String, String> entry : scenario.getParameters().entrySet()) {
command.add("-D" + entry.getKey() + "=" + entry.getValue());
}
- command.add(suiteClassName);
+ command.add(arguments.getSuiteClassName());
BufferedReader reader = null;
try {
@@ -202,7 +135,7 @@ public final class Runner {
Double nanosPerTrial = null;
try {
nanosPerTrial = Double.valueOf(firstLine);
- } catch (NumberFormatException e) {
+ } catch (NumberFormatException ignore) {
}
String anotherLine = reader.readLine();
@@ -229,163 +162,63 @@ public final class Runner {
}
}
- private Result runOutOfProcess() {
- ImmutableMap.Builder<Run, Double> resultsBuilder = ImmutableMap.builder();
+ // TODO: check if this is platform-independent
+ @SuppressWarnings("HardcodedLineSeparator")
+ private static final String RETURN = "\r";
+
+ private Run runOutOfProcess() {
+ String executedByUuid = getExecutedByUuid();
+ Date executedDate = new Date();
+ Builder<Scenario, Double> resultsBuilder = ImmutableMap.builder();
try {
- List<Run> runs = createRuns();
+ List<Scenario> scenarios = scenarioSelection.select();
int i = 0;
- for (Run run : runs) {
- beforeRun(i++, runs.size(), run);
- double nanosPerTrial = executeForked(run);
- afterRun(nanosPerTrial);
- resultsBuilder.put(run, nanosPerTrial);
+ for (Scenario scenario : scenarios) {
+ beforeMeasurement(i++, scenarios.size(), scenario);
+ double nanosPerTrial = executeForked(scenario);
+ afterMeasurement(nanosPerTrial);
+ resultsBuilder.put(scenario, nanosPerTrial);
}
// blat out our progress bar
- System.out.print("\r");
+ System.out.print(RETURN);
for (int j = 0; j < 80; j++) {
System.out.print(" ");
}
- System.out.print("\r");
+ System.out.print(RETURN);
- return new Result(resultsBuilder.build());
+ return new Run(resultsBuilder.build(), arguments.getSuiteClassName(), executedByUuid, executedDate);
} catch (Exception e) {
- throw new ExecutionException(e);
+ throw new ExceptionFromUserCodeException(e);
}
}
- private void beforeRun(int index, int total, Run run) {
+ private void beforeMeasurement(int index, int total, Scenario scenario) {
double percentDone = (double) index / total;
int runStringLength = 63; // so the total line length is 80
- String runString = String.valueOf(run);
+ String runString = String.valueOf(scenario);
if (runString.length() > runStringLength) {
runString = runString.substring(0, runStringLength);
}
- System.out.printf("\r%2.0f%% %-" + runStringLength + "s",
+ System.out.printf(RETURN + "%2.0f%% %-" + runStringLength + "s",
percentDone * 100, runString);
}
- private void afterRun(double nanosPerTrial) {
+ private void afterMeasurement(double nanosPerTrial) {
System.out.printf(" %10.0fns", nanosPerTrial);
}
- private void runInProcess() {
+ public static void main(String... args) {
try {
- Caliper caliper = new Caliper(warmupMillis, runMillis);
-
- for (Run run : createRuns()) {
- double result;
- TimedRunnable timedRunnable = suite.createBenchmark(run.getParameters());
- double warmupNanosPerTrial = caliper.warmUp(timedRunnable);
- result = caliper.run(timedRunnable, warmupNanosPerTrial);
- double nanosPerTrial = result;
- System.out.println(nanosPerTrial);
- }
- } catch (Exception e) {
- throw new ExecutionException(e);
- }
- }
-
- private boolean parseArgs(String[] args) throws Exception {
- for (int i = 0; i < args.length; i++) {
- if ("--help".equals(args[i])) {
- return false;
-
- } else if ("--inProcess".equals(args[i])) {
- inProcess = true;
-
- } else if (args[i].startsWith("-D")) {
- int equalsSign = args[i].indexOf('=');
- if (equalsSign == -1) {
- System.out.println("Malformed parameter " + args[i]);
- return false;
- }
- String name = args[i].substring(2, equalsSign);
- String value = args[i].substring(equalsSign + 1);
- setParameter(name, value);
-
- } else if ("--warmupMillis".equals(args[i])) {
- warmupMillis = Long.parseLong(args[++i]);
-
- } else if ("--runMillis".equals(args[i])) {
- runMillis = Long.parseLong(args[++i]);
-
- } else if ("--vm".equals(args[i])) {
- userVms.add(args[++i]);
-
- } else if (args[i].startsWith("-")) {
- System.out.println("Unrecognized option: " + args[i]);
- return false;
-
- } else {
- if (suiteClassName != null) {
- System.out.println("Too many benchmark classes!");
- return false;
- }
- suiteClassName = args[i];
- }
- }
-
- if (inProcess && !userVms.isEmpty()) {
- System.out.println("Cannot customize VM when running in process");
- return false;
- }
-
- if (suiteClassName == null) {
- System.out.println("No benchmark class provided.");
- return false;
- }
-
- return true;
- }
-
- private void printUsage() {
- System.out.println("Usage: Runner [OPTIONS...] <benchmark>");
- System.out.println();
- System.out.println(" <benchmark>: a benchmark class or suite");
- System.out.println();
- System.out.println("OPTIONS");
- System.out.println();
- System.out.println(" --D<param>=<value>: fix a benchmark parameter to a given value.");
- System.out.println(" When multiple values for the same parameter are given (via");
- System.out.println(" multiple --Dx=y args), all supplied values are used.");
- System.out.println();
- System.out.println(" --inProcess: run the benchmark in the same JVM rather than spawning");
- System.out.println(" another with the same classpath. By default each benchmark is");
- System.out.println(" run in a separate VM");
- System.out.println();
- System.out.println(" --warmupMillis <millis>: duration to warmup each benchmark");
- System.out.println();
- System.out.println(" --runMillis <millis>: duration to execute each benchmark");
- System.out.println();
- System.out.println(" --vm <vm>: executable to test benchmark on");
-
- // adding new options? don't forget to update executeForked()
- }
-
- public static void main(String... args) throws Exception { // TODO: cleaner error reporting
- Runner runner = new Runner();
- if (!runner.parseArgs(args)) {
- runner.printUsage();
- return;
+ new Runner().run(args);
+ } catch (UserException e) {
+ e.display();
+ System.exit(1);
}
-
- runner.prepareSuite();
- runner.prepareParameters();
- if (runner.inProcess) {
- runner.runInProcess();
- return;
- }
-
- Result result = runner.runOutOfProcess();
- new ConsoleReport(result).displayResults();
}
- public static void main(Class<? extends Benchmark> suite, String... args) throws Exception {
- String[] argsWithSuiteName = new String[args.length + 1];
- System.arraycopy(args, 0, argsWithSuiteName, 0, args.length);
- argsWithSuiteName[args.length] = suite.getName();
- main(argsWithSuiteName);
+ public static void main(Class<? extends Benchmark> suite, String... args) {
+ main(ObjectArrays.concat(args, suite.getName()));
}
}
diff --git a/src/com/google/caliper/Scenario.java b/src/com/google/caliper/Scenario.java
new file mode 100644
index 0000000..3fd06e4
--- /dev/null
+++ b/src/com/google/caliper/Scenario.java
@@ -0,0 +1,81 @@
+/*
+ * 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 java.io.Serializable;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A configured benchmark.
+ *
+ * <p>Gwt-safe.
+ */
+public final class Scenario
+ implements Serializable /* for GWT */ {
+
+ static final String VM_KEY = "vm";
+
+ /**
+ * The subset of variable names that are managed by the system. It is an error
+ * to create a parameter with the same name as one of these variables.
+ */
+ static final Set<String> SYSTEM_VARIABLES = new HashSet<String>(Arrays.asList(VM_KEY));
+
+ private /*final*/ Map<String, String> variables;
+
+ public Scenario(Map<String, String> variables) {
+ this.variables = new LinkedHashMap<String, String>(variables);
+ }
+
+ public Map<String, String> getVariables() {
+ return variables;
+ }
+
+ /**
+ * Returns the user-specified parameters. This is the (possibly-empty) set of
+ * variables that may be varied from scenario to scenario in the same
+ * environment.
+ */
+ public Map<String, String> getParameters() {
+ Map<String, String> result = new LinkedHashMap<String, String>();
+ for (Map.Entry<String, String> entry : variables.entrySet()) {
+ if (!SYSTEM_VARIABLES.contains(entry.getKey())) {
+ result.put(entry.getKey(), entry.getValue());
+ }
+ }
+ return result;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Scenario
+ && ((Scenario) o).getVariables().equals(variables);
+ }
+
+ @Override public int hashCode() {
+ return variables.hashCode();
+ }
+
+ @Override public String toString() {
+ return "Scenario" + variables;
+ }
+
+ private Scenario() {} // for GWT
+}
diff --git a/src/com/google/caliper/ScenarioSelection.java b/src/com/google/caliper/ScenarioSelection.java
new file mode 100644
index 0000000..7814818
--- /dev/null
+++ b/src/com/google/caliper/ScenarioSelection.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010 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.AbstractBenchmarkException;
+import com.google.caliper.UserException.DoesntImplementBenchmarkException;
+import com.google.caliper.UserException.ExceptionFromUserCodeException;
+import com.google.caliper.UserException.NoParameterlessConstructorException;
+import com.google.caliper.UserException.NoSuchClassException;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Figures out which scenarios to benchmark given a benchmark suite, set of user
+ * parameters, and set of user VMs.
+ */
+public final class ScenarioSelection {
+
+ private final String suiteClassName;
+ private final Multimap<String, String> userParameters;
+ private final Set<String> userVms;
+
+ private Benchmark suite;
+
+ /** Effective parameters to run in the benchmark. */
+ private final Multimap<String, String> parameters = LinkedHashMultimap.create();
+
+ public ScenarioSelection(Arguments arguments) {
+ this(arguments.getSuiteClassName(), arguments.getUserParameters(), arguments.getUserVms());
+ }
+
+ public ScenarioSelection(String suiteClassName,
+ Multimap<String, String> userParameters, Set<String> userVms) {
+ this.suiteClassName = suiteClassName;
+ this.userParameters = userParameters;
+ this.userVms = userVms;
+ }
+
+ /**
+ * Returns the selected scenarios for this benchmark.
+ */
+ public List<Scenario> select() {
+ prepareSuite();
+ prepareParameters();
+ return createScenarios();
+ }
+
+ public TimedRunnable createBenchmark(Scenario scenario) {
+ return suite.createBenchmark(scenario.getParameters());
+ }
+
+ private void prepareSuite() {
+ Class<?> benchmarkClass;
+ try {
+ benchmarkClass = getClassByName(suiteClassName);
+ } catch (ExceptionInInitializerError e) {
+ throw new ExceptionFromUserCodeException(e.getCause());
+ } catch (ClassNotFoundException ignored) {
+ throw new NoSuchClassException(suiteClassName);
+ }
+
+ Object s;
+ try {
+ Constructor<?> constructor = benchmarkClass.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ s = constructor.newInstance();
+ } catch (InstantiationException ignore) {
+ throw new AbstractBenchmarkException(benchmarkClass);
+ } catch (NoSuchMethodException ignore) {
+ throw new NoParameterlessConstructorException(benchmarkClass);
+ } catch (IllegalAccessException impossible) {
+ throw new AssertionError(impossible); // shouldn't happen since we setAccessible(true)
+ } catch (InvocationTargetException e) {
+ throw new ExceptionFromUserCodeException(e.getCause());
+ }
+
+ if (s instanceof Benchmark) {
+ this.suite = (Benchmark) s;
+ } else {
+ throw new DoesntImplementBenchmarkException(benchmarkClass);
+ }
+ }
+
+ private static Class<?> getClassByName(String className) throws ClassNotFoundException {
+ try {
+ return Class.forName(className);
+ } catch (ClassNotFoundException ignored) {
+ // try replacing the last dot with a $, in case that helps
+ // example: tutorial.Tutorial.Benchmark1 becomes tutorial.Tutorial$Benchmark1
+ // amusingly, the $ character means three different things in this one line alone
+ String newName = className.replaceFirst("\\.([^.]+)$", "\\$$1");
+ return Class.forName(newName);
+ }
+ }
+
+ private void prepareParameters() {
+ for (String key : suite.parameterNames()) {
+ // first check if the user has specified values
+ Collection<String> userValues = userParameters.get(key);
+ if (!userValues.isEmpty()) {
+ parameters.putAll(key, userValues);
+ // TODO: type convert 'em to validate?
+
+ } else { // otherwise use the default values from the suite
+ Set<String> values = suite.parameterValues(key);
+ if (values.isEmpty()) {
+ throw new ConfigurationException(key + " has no values");
+ }
+ parameters.putAll(key, values);
+ }
+ }
+ }
+
+ private ImmutableSet<String> defaultVms() {
+ return "Dalvik".equals(System.getProperty("java.vm.name"))
+ ? ImmutableSet.of("dalvikvm")
+ : ImmutableSet.of("java");
+ }
+
+ /**
+ * Returns a complete set of scenarios with every combination of values and
+ * benchmark classes.
+ */
+ private List<Scenario> createScenarios() {
+ List<ScenarioBuilder> builders = new ArrayList<ScenarioBuilder>();
+
+ // create scenarios for each VM
+ Set<String> vms = userVms.isEmpty()
+ ? defaultVms()
+ : userVms;
+ for (String vm : vms) {
+ ScenarioBuilder scenarioBuilder = new ScenarioBuilder();
+ scenarioBuilder.parameters.put(Scenario.VM_KEY, vm);
+ builders.add(scenarioBuilder);
+ }
+
+ for (Entry<String, Collection<String>> parameter : parameters.asMap().entrySet()) {
+ Iterator<String> values = parameter.getValue().iterator();
+ if (!values.hasNext()) {
+ throw new ConfigurationException("Not enough values for " + parameter);
+ }
+
+ String key = parameter.getKey();
+
+ String firstValue = values.next();
+ for (ScenarioBuilder builder : builders) {
+ builder.parameters.put(key, firstValue);
+ }
+
+ // multiply the size of the specs by the number of alternate values
+ int size = builders.size();
+ while (values.hasNext()) {
+ String alternate = values.next();
+ for (int s = 0; s < size; s++) {
+ ScenarioBuilder copy = builders.get(s).copy();
+ copy.parameters.put(key, alternate);
+ builders.add(copy);
+ }
+ }
+ }
+
+ List<Scenario> result = new ArrayList<Scenario>();
+ for (ScenarioBuilder builder : builders) {
+ result.add(builder.build());
+ }
+
+ return result;
+ }
+
+ private static class ScenarioBuilder {
+ final Map<String, String> parameters = new LinkedHashMap<String, String>();
+
+ ScenarioBuilder copy() {
+ ScenarioBuilder result = new ScenarioBuilder();
+ result.parameters.putAll(parameters);
+ return result;
+ }
+
+ public Scenario build() {
+ return new Scenario(parameters);
+ }
+ }
+}
diff --git a/src/com/google/caliper/SimpleBenchmark.java b/src/com/google/caliper/SimpleBenchmark.java
index 8d2d4b1..31ff6c9 100644
--- a/src/com/google/caliper/SimpleBenchmark.java
+++ b/src/com/google/caliper/SimpleBenchmark.java
@@ -16,12 +16,11 @@
package com.google.caliper;
+import com.google.caliper.UserException.ExceptionFromUserCodeException;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
@@ -88,34 +87,31 @@ public abstract class SimpleBenchmark implements Benchmark {
return methods.keySet();
}
+ Parameter<?> parameter = parameters.get(parameterName);
+ if (parameter == null) {
+ throw new IllegalArgumentException();
+ }
try {
- TypeConverter typeConverter = new TypeConverter();
- Parameter<?> parameter = parameters.get(parameterName);
- if (parameter == null) {
- throw new IllegalArgumentException();
- }
Collection<?> values = parameter.values();
- Type type = parameter.getType();
ImmutableSet.Builder<String> result = ImmutableSet.builder();
for (Object value : values) {
- result.add(typeConverter.toString(value, type));
+ result.add(String.valueOf(value));
}
return result.build();
} catch (Exception e) {
- throw new ExecutionException(e);
+ throw new ExceptionFromUserCodeException(e);
}
}
public TimedRunnable createBenchmark(Map<String, String> parameterValues) {
- TypeConverter typeConverter = new TypeConverter();
-
if (!parameterNames().equals(parameterValues.keySet())) {
throw new IllegalArgumentException("Invalid parameters specified. Expected "
+ parameterNames() + " but was " + parameterValues.keySet());
}
try {
+ @SuppressWarnings({"ClassNewInstance"}) // can throw any Exception, so we catch all Exceptions
final SimpleBenchmark copyOfSelf = getClass().newInstance();
final Method method = methods.get(parameterValues.get("benchmark"));
@@ -125,8 +121,8 @@ public abstract class SimpleBenchmark implements Benchmark {
continue;
}
- Parameter parameter = parameters.get(parameterName);
- Object value = typeConverter.fromString(entry.getValue(), parameter.getType());
+ Parameter<?> parameter = parameters.get(parameterName);
+ Object value = TypeConverter.fromString(entry.getValue(), parameter.getType());
parameter.set(copyOfSelf, value);
}
copyOfSelf.setUp();
@@ -138,7 +134,7 @@ public abstract class SimpleBenchmark implements Benchmark {
};
} catch (Exception e) {
- throw new ExecutionException(e);
+ throw new ExceptionFromUserCodeException(e);
}
}
@@ -148,7 +144,7 @@ public abstract class SimpleBenchmark implements Benchmark {
*/
private Map<String, Method> createTimedMethods() {
ImmutableMap.Builder<String, Method> result = ImmutableMap.builder();
- for (final Method method : getClass().getDeclaredMethods()) {
+ for (Method method : getClass().getDeclaredMethods()) {
int modifiers = method.getModifiers();
if (!method.getName().startsWith("time")) {
continue;
diff --git a/src/com/google/caliper/TypeConverter.java b/src/com/google/caliper/TypeConverter.java
index 73300ec..29d00ea 100644
--- a/src/com/google/caliper/TypeConverter.java
+++ b/src/com/google/caliper/TypeConverter.java
@@ -16,42 +16,44 @@
package com.google.caliper;
+import com.google.common.collect.ImmutableMap;
+import java.lang.reflect.Method;
import java.lang.reflect.Type;
+import java.util.Map;
/**
* Convert objects to and from Strings.
*/
-class TypeConverter {
+final class TypeConverter {
+ private TypeConverter() {}
- // the enum to strings conversion is manually checked
- @SuppressWarnings("unchecked")
- public Object fromString(String value, Type type) {
- if (type instanceof Class) {
- Class<?> c = (Class<?>) type;
- if (c.isEnum()) {
- return Enum.valueOf((Class) c, value);
- } else if (type == Double.class || type == double.class) {
- return Double.valueOf(value);
- } else if (type == Integer.class || type == int.class) {
- return Integer.valueOf(value);
- }
+ public static Object fromString(String value, Type type) {
+ Class<?> c = wrap((Class<?>) type);
+ try {
+ Method m = c.getMethod("valueOf", String.class);
+ return m.invoke(null, value);
+ } catch (Exception e) {
+ throw new UnsupportedOperationException(
+ "Cannot convert " + value + " of type " + type, e);
}
- throw new UnsupportedOperationException(
- "Cannot convert " + value + " of type " + type);
}
- public String toString(Object value, Type type) {
- if (type instanceof Class) {
- Class<?> c = (Class<?>) type;
- if (c.isEnum()) {
- return value.toString();
- } else if (type == Double.class || type == double.class) {
- return value.toString();
- } else if (type == Integer.class || type == int.class) {
- return value.toString();
- }
- }
- throw new UnsupportedOperationException(
- "Cannot convert " + value + " of type " + type);
+ // safe because both Long.class and long.class are of type Class<Long>
+ @SuppressWarnings("unchecked")
+ private static <T> Class<T> wrap(Class<T> c) {
+ return c.isPrimitive() ? (Class<T>) PRIMITIVES_TO_WRAPPERS.get(c) : c;
}
+
+ private static final Map<Class<?>, Class<?>> PRIMITIVES_TO_WRAPPERS
+ = new ImmutableMap.Builder<Class<?>, Class<?>>()
+ .put(boolean.class, Boolean.class)
+ .put(byte.class, Byte.class)
+ .put(char.class, Character.class)
+ .put(double.class, Double.class)
+ .put(float.class, Float.class)
+ .put(int.class, Integer.class)
+ .put(long.class, Long.class)
+ .put(short.class, Short.class)
+ .put(void.class, Void.class)
+ .build();
}
diff --git a/src/com/google/caliper/UserException.java b/src/com/google/caliper/UserException.java
new file mode 100644
index 0000000..66fb8e3
--- /dev/null
+++ b/src/com/google/caliper/UserException.java
@@ -0,0 +1,141 @@
+/*
+ * 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 java.util.Arrays;
+
+/**
+ * Signifies a problem that should be explained in user-friendly terms on the command line, without
+ * a confusing stack trace, and optionally followed by a usage summary.
+ */
+@SuppressWarnings("serial") // never going to serialize these... right?
+public abstract class UserException extends RuntimeException {
+ protected final String error;
+
+ protected UserException(String error) {
+ this.error = error;
+ }
+
+ public abstract void display();
+
+ // - - - -
+
+ public abstract static class ErrorInUsageException extends UserException {
+ protected ErrorInUsageException(String error) {
+ super(error);
+ }
+
+ @Override public void display() {
+ if (error != null) {
+ System.err.println("Error: " + error);
+ }
+ Arguments.printUsage();
+ }
+ }
+
+ public abstract static class ErrorInUserCodeException extends UserException {
+ private final String remedy;
+
+ protected ErrorInUserCodeException(String error, String remedy) {
+ super(error);
+ this.remedy = remedy;
+ }
+
+ @Override public void display() {
+ System.err.println("Error: " + error);
+ System.err.println("Typical Remedy: " + remedy);
+ }
+ }
+
+ // - - - -
+
+ // Not technically an error, but works nicely this way anyway
+ public static class DisplayUsageException extends ErrorInUsageException {
+ public DisplayUsageException() {
+ super(null);
+ }
+ }
+
+ public static class UnrecognizedOptionException extends ErrorInUsageException {
+ public UnrecognizedOptionException(String arg) {
+ super("Argument not recognized: " + arg);
+ }
+ }
+
+ public static class NoBenchmarkClassException extends ErrorInUsageException {
+ public NoBenchmarkClassException() {
+ super("No benchmark class specified.");
+ }
+ }
+
+ public static class MultipleBenchmarkClassesException extends ErrorInUsageException {
+ public MultipleBenchmarkClassesException(String a, String b) {
+ super("Multiple benchmark classes specified: " + Arrays.asList(a, b));
+ }
+ }
+
+ public static class MalformedParameterException extends ErrorInUsageException {
+ public MalformedParameterException(String arg) {
+ super("Malformed parameter: " + arg);
+ }
+ }
+
+ public static class CantCustomizeInProcessVmException extends ErrorInUsageException {
+ public CantCustomizeInProcessVmException() {
+ super("Can't customize VM when running in process.");
+ }
+ }
+
+ public static class NoSuchClassException extends ErrorInUsageException {
+ public NoSuchClassException(String name) {
+ super("No class named [" + name + "] was found (check CLASSPATH).");
+ }
+ }
+
+
+ public static class AbstractBenchmarkException extends ErrorInUserCodeException {
+ public AbstractBenchmarkException(Class<?> specifiedClass) {
+ super("Class [" + specifiedClass.getName() + "] is abstract.", "Specify a concrete class.");
+ }
+ }
+
+ public static class NoParameterlessConstructorException extends ErrorInUserCodeException {
+ public NoParameterlessConstructorException(Class<?> specifiedClass) {
+ super("Class [" + specifiedClass.getName() + "] has no parameterless constructor.",
+ "Remove all constructors or add a parameterless constructor.");
+ }
+ }
+
+ public static class DoesntImplementBenchmarkException extends ErrorInUserCodeException {
+ public DoesntImplementBenchmarkException(Class<?> specifiedClass) {
+ super("Class [" + specifiedClass + "] does not implement the " + Benchmark.class.getName()
+ + " interface.", "Add 'extends " + SimpleBenchmark.class + "' to the class declaration.");
+ }
+ }
+
+ // TODO: should remove the caliper stack frames....
+ public static class ExceptionFromUserCodeException extends UserException {
+ public ExceptionFromUserCodeException(Throwable t) {
+ super("An exception was thrown from the benchmark code.");
+ initCause(t);
+ }
+ @Override public void display() {
+ System.err.println(error);
+ getCause().printStackTrace(System.err);
+ }
+ }
+}
diff --git a/src/com/google/caliper/Xml.java b/src/com/google/caliper/Xml.java
new file mode 100644
index 0000000..f7cfafc
--- /dev/null
+++ b/src/com/google/caliper/Xml.java
@@ -0,0 +1,108 @@
+/**
+ * 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.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+public final class Xml {
+ private static final String DATE_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssz";
+
+ /**
+ * Encodes this result as XML to the specified stream. This XML can be parsed
+ * with {@link #runFromXml(InputStream)}. Sample output:
+ * <pre>{@code
+ * <result benchmark="examples.FooBenchmark"
+ * executedBy="A0:1F:CAFE:BABE"
+ * executedTimestamp="2010-01-05T11:08:15PST">
+ * <scenario bar="15" foo="A" vm="dalvikvm">1200.1</scenario>
+ * <scenario bar="15" foo="B" vm="dalvikvm">1100.2</scenario>
+ * </result>
+ * }</pre>
+ */
+ public static void runToXml(Run run, OutputStream out) {
+ // BEGIN android-removed
+ // we don't have DOM level 3 on Android yet
+ // try {
+ // Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ // Element result = doc.createElement("result");
+ // doc.appendChild(result);
+ //
+ // result.setAttribute("benchmark", run.getBenchmarkName());
+ // result.setAttribute("executedBy", run.getExecutedByUuid());
+ // String executedTimestampString = new SimpleDateFormat(DATE_FORMAT_STRING)
+ // .format(run.getExecutedTimestamp());
+ // result.setAttribute("executedTimestamp", executedTimestampString);
+ //
+ // for (Map.Entry<Scenario, Double> entry : run.getMeasurements().entrySet()) {
+ // Element runElement = doc.createElement("scenario");
+ // result.appendChild(runElement);
+ //
+ // Scenario scenario = entry.getKey();
+ // for (Map.Entry<String, String> parameter : scenario.getVariables().entrySet()) {
+ // runElement.setAttribute(parameter.getKey(), parameter.getValue());
+ // }
+ // runElement.setTextContent(String.valueOf(entry.getValue()));
+ // }
+ //
+ // TransformerFactory.newInstance().newTransformer()
+ // .transform(new DOMSource(doc), new StreamResult(out));
+ // } catch (Exception e) {
+ // throw new IllegalStateException("Malformed XML document", e);
+ // }
+ // END android-removed
+ }
+
+ /**
+ * Creates a result by decoding XML from the specified stream. The XML should
+ * be consistent with the format emitted by {@link #runToXml(Run, OutputStream)}.
+ */
+ public static Run runFromXml(InputStream in) {
+ // BEGIN android-removed
+ // we don't have DOM level 3 on Android yet
+ throw new UnsupportedOperationException();
+ // try {
+ // Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+ // Element result = document.getDocumentElement();
+ //
+ // String benchmarkName = result.getAttribute("benchmark");
+ // String executedByUuid = result.getAttribute("executedBy");
+ // String executedDateString = result.getAttribute("executedTimestamp");
+ // Date executedDate = new SimpleDateFormat(DATE_FORMAT_STRING).parse(executedDateString);
+ //
+ // ImmutableMap.Builder<Scenario, Double> measurementsBuilder = ImmutableMap.builder();
+ // for (Node node : childrenOf(result)) {
+ // Element scenarioElement = (Element) node;
+ // Scenario scenario = new Scenario(attributesOf(scenarioElement));
+ // double measurement = Double.parseDouble(scenarioElement.getTextContent());
+ // measurementsBuilder.put(scenario, measurement);
+ // }
+ //
+ // return new Run(measurementsBuilder.build(), benchmarkName, executedByUuid, executedDate);
+ // } catch (Exception e) {
+ // throw new IllegalStateException("Malformed XML document", e);
+ // }
+ // END android-removed
+ }
+
+ private Xml() {}
+}