Added version 0.5-rc1

Change-Id: Ic21eda3c659eadf3a4229ae5618e9ce1e96b4aa7
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..c747764
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,43 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# build caliper jar
+# ============================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := caliper-target
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_SRC_FILES := $(call all-java-files-under, caliper/src/main/java/)
+LOCAL_JAVA_RESOURCE_DIRS := caliper/src/main/resources
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+  caliper-gson \
+  caliper-java-allocation-instrumenter \
+  guava
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Build dependencies.
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+    caliper-gson:lib/gson-1.7.1$(COMMON_JAVA_PACKAGE_SUFFIX) \
+    caliper-java-allocation-instrumenter:lib/java-allocation-instrumenter-2.0$(COMMON_JAVA_PACKAGE_SUFFIX)
+
+include $(BUILD_MULTI_PREBUILT)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/README b/README
new file mode 100644
index 0000000..c54683c
--- /dev/null
+++ b/README
@@ -0,0 +1,8 @@
+To build this project with Maven:
+
+1. cd caliper
+mvn eclipse:configure-workspace eclipse:eclipse install
+
+2. To build examples
+cd examples
+mvn eclipse:configure-workspace eclipse:eclipse install
diff --git a/caliper/pom.xml b/caliper/pom.xml
new file mode 100644
index 0000000..befd583
--- /dev/null
+++ b/caliper/pom.xml
@@ -0,0 +1,134 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.google.caliper</groupId>
+  <artifactId>caliper</artifactId>
+  <packaging>jar</packaging>
+  <version>0.5-rc1</version>
+  <inceptionYear>2009</inceptionYear>
+  <name>caliper</name>
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
+  </parent>
+  <url>http://code.google.com/p/caliper/</url>
+  <description>Caliper: Microbenchmarking Framework for Java</description>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+  <licenses>
+    <license>
+      <name>The Apache Software License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+  <scm>
+    <connection>scm:git:http://code.google.com/p/caliper/</connection>
+    <developerConnection>scm:git:http://code.google.com/p/caliper/</developerConnection>
+    <url>http://code.google.com/p/caliper/source/browse</url>
+  </scm>
+  <issueManagement>
+    <system>Google Code Issue Tracking</system>
+    <url>http://code.google.com/p/caliper/issues/list</url>
+  </issueManagement>
+  <organization>
+    <name>Google, Inc.</name>
+    <url>http://www.google.com</url>
+  </organization>
+  <dependencies>
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+      <version>1.3.9</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+      <version>1.7.1</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <version>11.0.1</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.java-allocation-instrumenter</groupId>
+      <artifactId>java-allocation-instrumenter</artifactId>
+      <version>2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.2</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <defaultGoal>package</defaultGoal>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.3.2</version>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-eclipse-plugin</artifactId>
+        <version>2.8</version>
+        <configuration>
+          <downloadSources>true</downloadSources>
+          <downloadJavadocs>true</downloadJavadocs>
+          <workspace>../eclipse-ws/</workspace>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <version>2.1.2</version>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>jar-no-fork</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.7</version>
+        <executions>
+          <execution>
+            <id>generate-javadocs</id>
+            <phase>site</phase>
+            <goals>
+              <goal>javadoc</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <links>
+            <link>http://download.oracle.com/javase/1.5.0/docs/api/</link>
+          </links>
+          <version>true</version>
+          <show>public</show>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <developers>
+    <developer>
+      <name>Jesse Wilson</name>
+      <organization>Google Inc.</organization>
+    </developer>
+  </developers>
+</project>
diff --git a/caliper/src/main/java/com/google/caliper/AllocationMeasurer.java b/caliper/src/main/java/com/google/caliper/AllocationMeasurer.java
new file mode 100644
index 0000000..2ec3488
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/AllocationMeasurer.java
@@ -0,0 +1,165 @@
+/*
+ * 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.NonConstantMemoryUsage;
+import com.google.common.base.Supplier;
+import com.google.monitoring.runtime.instrumentation.AllocationRecorder;
+import com.google.monitoring.runtime.instrumentation.Sampler;
+
+public abstract class AllocationMeasurer extends Measurer {
+
+  protected static final int ALLOCATION_DISPLAY_THRESHOLD = 50;
+
+  private boolean log;
+  private long tempAllocationCount;
+  private long allocationsToIgnore;
+  private long numberOfAllocations;
+  private long allocationCount;
+  private long outOfThreadAllocationCount;
+  private boolean recordAllocations;
+  protected String type;
+
+  protected AllocationMeasurer() {
+    log = false;
+    allocationsToIgnore = 0;
+    numberOfAllocations = 0;
+    allocationCount = 0;
+    outOfThreadAllocationCount = 0;
+    recordAllocations = false;
+
+    final Thread allocatingThread = Thread.currentThread();
+    AllocationRecorder.addSampler(new Sampler() {
+      // allocated {@code newObj} of type {@code desc}, whose size is {@code size}.
+      // if this was not an array, {@code count} is -1. If it was array, {@code count} is the
+      // size of the array.
+      @Override public void sampleAllocation(int count, String desc, Object newObj, long size) {
+        if (recordAllocations) {
+          if (Thread.currentThread().equals(allocatingThread)) {
+            if (log) {
+              logAllocation(count, desc, size);
+            } else if (numberOfAllocations == 0) {
+              log("see first run for list of allocations");
+            }
+            allocationCount = incrementAllocationCount(allocationCount, count, size);
+            tempAllocationCount++;
+            numberOfAllocations++;
+          } else {
+            outOfThreadAllocationCount = incrementAllocationCount(outOfThreadAllocationCount, count, size);
+            numberOfAllocations++;
+          }
+        }
+      }
+    });
+  }
+
+  protected abstract long incrementAllocationCount(long orig, int count, long size);
+
+  private void logAllocation(int count, String desc, long size) {
+    if (numberOfAllocations >= allocationsToIgnore) {
+      if (numberOfAllocations < ALLOCATION_DISPLAY_THRESHOLD + allocationsToIgnore) {
+        log("allocating " + desc + (count == -1 ? "" : " array with " + count + " elements")
+            + " with size " + size + " bytes");
+      } else if (numberOfAllocations == ALLOCATION_DISPLAY_THRESHOLD + allocationsToIgnore) {
+        log("...more allocations...");
+      }
+    }
+  }
+
+  @Override public MeasurementSet run(Supplier<ConfiguredBenchmark> testSupplier) throws Exception {
+
+    // warm up, for some reason the very first time anything is measured, it will have a few more
+    // allocations.
+    measureAllocations(testSupplier.get(), 1, 0);
+
+    // The "one" case serves as a base line. There may be caching, lazy loading, etc going on here.
+    tempAllocationCount = 0; // count the number of times the sampler is called in one rep
+    long one = measureAllocationsTotal(testSupplier.get(), 1);
+    long oneAllocations = tempAllocationCount;
+
+    // we expect that the delta between any two consecutive reps will be constant
+    tempAllocationCount = 0; // count the number of times the sampler is called in two reps
+    long two = measureAllocationsTotal(testSupplier.get(), 2);
+    long twoAllocations = tempAllocationCount;
+    long expectedDiff = two - one;
+    // there is some overhead on the first call that we can ignore for the purposes of measurement
+    long unitsToIgnore = one - expectedDiff;
+    allocationsToIgnore = 2 * oneAllocations - twoAllocations;
+    log("ignoring " + allocationsToIgnore + " allocation(s) per measurement as overhead");
+
+    Measurement[] allocationMeasurements = new Measurement[4];
+    log = true;
+    allocationMeasurements[0] = measureAllocations(testSupplier.get(), 1, unitsToIgnore);
+    log = false;
+    for (int i = 1; i < allocationMeasurements.length; i++) {
+      allocationMeasurements[i] =
+          measureAllocations(testSupplier.get(), i + 1, unitsToIgnore);
+      if (Math.round(allocationMeasurements[i].getRaw()) != expectedDiff) {
+        throw new NonConstantMemoryUsage();
+      }
+    }
+
+    // The above logic guarantees that all the measurements are equal, so we only need to return a
+    // single measurement.
+    allocationsToIgnore = 0;
+    return new MeasurementSet(allocationMeasurements[0]);
+  }
+
+  private Measurement measureAllocations(ConfiguredBenchmark benchmark, int reps, long toIgnore)
+      throws Exception {
+    prepareForTest();
+    log(LogConstants.MEASURED_SECTION_STARTING);
+    resetAllocations();
+    recordAllocations = true;
+    benchmark.run(reps);
+    recordAllocations = false;
+    log(LogConstants.MEASURED_SECTION_DONE);
+    long allocations = (allocationCount - toIgnore) / reps;
+    long outOfThreadAllocations = outOfThreadAllocationCount;
+    log(allocations + " " + type + "(s) allocated per rep");
+    log(outOfThreadAllocations + " out of thread " + type + "(s) allocated in " + reps + " reps");
+    benchmark.close();
+    return getMeasurement(benchmark, allocations);
+  }
+
+  protected abstract Measurement getMeasurement(ConfiguredBenchmark benchmark, long allocations);
+
+  private long measureAllocationsTotal(ConfiguredBenchmark benchmark, int reps)
+      throws Exception {
+    prepareForTest();
+    log(LogConstants.MEASURED_SECTION_STARTING);
+    resetAllocations();
+    recordAllocations = true;
+    benchmark.run(reps);
+    recordAllocations = false;
+    log(LogConstants.MEASURED_SECTION_DONE);
+    long allocations = allocationCount;
+    long outOfThreadAllocations = outOfThreadAllocationCount;
+    log(allocations + " " + type + "(s) allocated in " + reps + " reps");
+    if (outOfThreadAllocations > 0) {
+      log(outOfThreadAllocations + " out of thread " + type + "(s) allocated in " + reps + " reps");
+    }
+    benchmark.close();
+    return allocations;
+  }
+
+  private void resetAllocations() {
+    allocationCount = 0;
+    outOfThreadAllocationCount = 0;
+    numberOfAllocations = 0;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Arguments.java b/caliper/src/main/java/com/google/caliper/Arguments.java
new file mode 100644
index 0000000..cceb079
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Arguments.java
@@ -0,0 +1,420 @@
+/*
+ * 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.IncompatibleArgumentsException;
+import com.google.caliper.UserException.InvalidParameterValueException;
+import com.google.caliper.UserException.MultipleBenchmarkClassesException;
+import com.google.caliper.UserException.NoBenchmarkClassException;
+import com.google.caliper.UserException.UnrecognizedOptionException;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.Map;
+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 = Sets.newLinkedHashSet();
+
+  /**
+   * 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();
+
+  /**
+   * VM parameters like {memory=[-Xmx64,-Xmx128],jit=[-client,-server]}
+   */
+  private final Multimap<String, String> vmParameters = LinkedHashMultimap.create();
+
+  private int trials = 1;
+  private long warmupMillis = 3000;
+  private long runMillis = 1000;
+  private String timeUnit = null;
+  private String instanceUnit = null;
+  private String memoryUnit = null;
+  private File saveResultsFile = null;
+  private File uploadResultsFile = null;
+  private boolean captureVmLog = false;
+  private boolean printScore = false;
+  private boolean measureMemory = false;
+  private boolean debug = false;
+  private int debugReps = defaultDebugReps;
+  private MeasurementType measurementType;
+  private MeasurementType primaryMeasurementType;
+
+  /**
+   * A signal that indicates a JSON object interleaved with the process output.
+   * Should be short, but unlikely to show up in the processes natural output.
+   */
+  private String marker = "//ZxJ/";
+
+  private static final String defaultDelimiter = ",";
+  private static final int defaultDebugReps = 1000;
+
+  public String getSuiteClassName() {
+    return suiteClassName;
+  }
+
+  public Set<String> getUserVms() {
+    return userVms;
+  }
+
+  public int getTrials() {
+    return trials;
+  }
+
+  public Multimap<String, String> getVmParameters() {
+    return vmParameters;
+  }
+
+  public Multimap<String, String> getUserParameters() {
+    return userParameters;
+  }
+
+  public long getWarmupMillis() {
+    return warmupMillis;
+  }
+
+  public long getRunMillis() {
+    return runMillis;
+  }
+
+  public String getTimeUnit() {
+    return timeUnit;
+  }
+
+  public String getInstanceUnit() {
+    return instanceUnit;
+  }
+
+  public String getMemoryUnit() {
+    return memoryUnit;
+  }
+
+  public File getSaveResultsFile() {
+    return saveResultsFile;
+  }
+
+  public File getUploadResultsFile() {
+    return uploadResultsFile;
+  }
+
+  public boolean getCaptureVmLog() {
+    return captureVmLog;
+  }
+
+  public boolean printScore() {
+    return printScore;
+  }
+
+  public boolean getMeasureMemory() {
+    return measureMemory;
+  }
+
+  public MeasurementType getMeasurementType() {
+    return measurementType;
+  }
+
+  public MeasurementType getPrimaryMeasurementType() {
+    return primaryMeasurementType;
+  }
+
+  public boolean getDebug() {
+    return debug;
+  }
+
+  public int getDebugReps() {
+    return debugReps;
+  }
+
+  public String getMarker() {
+    return marker;
+  }
+
+  public static Arguments parse(String[] argsArray) {
+    Arguments result = new Arguments();
+
+    Iterator<String> args = Iterators.forArray(argsArray);
+    String delimiter = defaultDelimiter;
+    Map<String, String> userParameterStrings = Maps.newLinkedHashMap();
+    Map<String, String> vmParameterStrings = Maps.newLinkedHashMap();
+    String vmString = null;
+    boolean standardRun = false;
+    while (args.hasNext()) {
+      String arg = args.next();
+
+      if ("--help".equals(arg)) {
+        throw new DisplayUsageException();
+      }
+
+      if (arg.startsWith("-D") || arg.startsWith("-J")) {
+
+        /*
+         * Handle user parameters (-D) and VM parameters (-J) of these forms:
+         *
+         * -Dlength=100
+         * -Jmemory=-Xmx1024M
+         * -Dlength=100,200
+         * -Jmemory=-Xmx1024M,-Xmx2048M
+         * -Dlength 100
+         * -Jmemory -Xmx1024M
+         * -Dlength 100,200
+         * -Jmemory -Xmx1024M,-Xmx2048M
+         */
+
+        String name;
+        String value;
+        int equalsSign = arg.indexOf('=');
+        if (equalsSign == -1) {
+          name = arg.substring(2);
+          value = args.next();
+        } else {
+          name = arg.substring(2, equalsSign);
+          value = arg.substring(equalsSign + 1);
+        }
+
+        String previousValue;
+        if (arg.startsWith("-D")) {
+          previousValue = userParameterStrings.put(name, value);
+        } else {
+          previousValue = vmParameterStrings.put(name, value);
+        }
+        if (previousValue != null) {
+          throw new UserException.DuplicateParameterException(arg);
+        }
+        standardRun = true;
+      // TODO: move warmup/run to caliperrc
+      } else if ("--captureVmLog".equals(arg)) {
+        result.captureVmLog = true;
+        standardRun = true;
+      } else if ("--warmupMillis".equals(arg)) {
+        result.warmupMillis = Long.parseLong(args.next());
+        standardRun = true;
+      } else if ("--runMillis".equals(arg)) {
+        result.runMillis = Long.parseLong(args.next());
+        standardRun = true;
+      } else if ("--trials".equals(arg)) {
+        String value = args.next();
+        try {
+          result.trials = Integer.parseInt(value);
+          if (result.trials < 1) {
+            throw new UserException.InvalidTrialsException(value);
+          }
+        } catch (NumberFormatException e) {
+          throw new UserException.InvalidTrialsException(value);
+        }
+        standardRun = true;
+      } else if ("--vm".equals(arg)) {
+        if (vmString != null) {
+          throw new UserException.DuplicateParameterException(arg);
+        }
+        vmString = args.next();
+        standardRun = true;
+      } else if ("--delimiter".equals(arg)) {
+        delimiter = args.next();
+        standardRun = true;
+      } else if ("--timeUnit".equals(arg)) {
+        result.timeUnit = args.next();
+        standardRun = true;
+      } else if ("--instanceUnit".equals(arg)) {
+        result.instanceUnit = args.next();
+        standardRun = true;
+      } else if ("--memoryUnit".equals(arg)) {
+        result.memoryUnit = args.next();
+        standardRun = true;
+      } else if ("--saveResults".equals(arg) || "--xmlSave".equals(arg)) {
+        // TODO: unsupport legacy --xmlSave
+        result.saveResultsFile = new File(args.next());
+        standardRun = true;
+      } else if ("--uploadResults".equals(arg)) {
+        result.uploadResultsFile = new File(args.next());
+      } else if ("--printScore".equals(arg)) {
+        result.printScore = true;
+        standardRun = true;
+      } else if ("--measureMemory".equals(arg)) {
+        result.measureMemory = true;
+        standardRun = true;
+      } else if ("--debug".equals(arg)) {
+        result.debug = true;
+      } else if ("--debug-reps".equals(arg)) {
+        String value = args.next();
+        try {
+          result.debugReps = Integer.parseInt(value);
+          if (result.debugReps < 1) {
+            throw new UserException.InvalidDebugRepsException(value);
+          }
+        } catch (NumberFormatException e) {
+          throw new UserException.InvalidDebugRepsException(value);
+        }
+      } else if ("--marker".equals(arg)) {
+        result.marker = args.next();
+      } else if ("--measurementType".equals(arg)) {
+        String measurementType = args.next();
+        try {
+          result.measurementType = MeasurementType.valueOf(measurementType);
+        } catch (Exception e) {
+          throw new InvalidParameterValueException(arg, measurementType);
+        }
+        standardRun = true;
+      } else if ("--primaryMeasurementType".equals(arg)) {
+        String measurementType = args.next().toUpperCase();
+        try {
+          result.primaryMeasurementType = MeasurementType.valueOf(measurementType);
+        } catch (Exception e) {
+          throw new InvalidParameterValueException(arg, measurementType);
+        }
+        standardRun = true;
+      } else if (arg.startsWith("-")) {
+        throw new UnrecognizedOptionException(arg);
+
+      } else {
+        if (result.suiteClassName != null) {
+          throw new MultipleBenchmarkClassesException(result.suiteClassName, arg);
+        }
+        result.suiteClassName = arg;
+      }
+    }
+
+    Splitter delimiterSplitter = Splitter.on(delimiter);
+
+    if (vmString != null) {
+      Iterables.addAll(result.userVms, delimiterSplitter.split(vmString));
+    }
+
+    Set<String> duplicates = Sets.intersection(
+        userParameterStrings.keySet(), vmParameterStrings.keySet());
+    if (!duplicates.isEmpty()) {
+      throw new UserException.DuplicateParameterException(duplicates);
+    }
+
+    for (Map.Entry<String, String> entry : userParameterStrings.entrySet()) {
+      result.userParameters.putAll(entry.getKey(), delimiterSplitter.split(entry.getValue()));
+    }
+    for (Map.Entry<String, String> entry : vmParameterStrings.entrySet()) {
+      result.vmParameters.putAll(entry.getKey(), delimiterSplitter.split(entry.getValue()));
+    }
+
+    if (standardRun && result.uploadResultsFile != null) {
+      throw new IncompatibleArgumentsException("--uploadResults");
+    }
+
+    if (result.suiteClassName == null && result.uploadResultsFile == null) {
+      throw new NoBenchmarkClassException();
+    }
+
+    if (result.primaryMeasurementType != null
+        && result.primaryMeasurementType != MeasurementType.TIME && !result.measureMemory) {
+      throw new IncompatibleArgumentsException(
+          "--primaryMeasurementType " + result.primaryMeasurementType.toString().toLowerCase());
+    }
+
+    return result;
+  }
+
+  public static void printUsage() {
+    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("        Multiple values can be supplied by separating them with the");
+    System.out.println("        delimiter specified in the --delimiter argument.");
+    System.out.println();
+    System.out.println("        For example: \"-Dfoo=bar,baz,bat\"");
+    System.out.println();
+    System.out.println("        \"benchmark\" is a special parameter that can be used to specify");
+    System.out.println("        which benchmark methods to run. For example, if a benchmark has");
+    System.out.println("        the method \"timeFoo\", it can be run alone by using");
+    System.out.println("        \"-Dbenchmark=Foo\". \"benchmark\" also accepts a delimiter");
+    System.out.println("        separated list of methods to run.");
+    System.out.println();
+    System.out.println("  -J<param>=<value>: set a JVM argument to the given value.");
+    System.out.println("        Multiple values can be supplied by separating them with the");
+    System.out.println("        delimiter specified in the --delimiter argument.");
+    System.out.println();
+    System.out.println("        For example: \"-JmemoryMax=-Xmx32M,-Xmx512M\"");
+    System.out.println();
+    System.out.println("  --delimiter <delimiter>: character or string to use as a delimiter");
+    System.out.println("        for parameter and vm values.");
+    System.out.println("        Default: \"" + defaultDelimiter + "\"");
+    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("  --captureVmLog: record the VM's just-in-time compiler and GC logs.");
+    System.out.println("        This may slow down or break benchmark display tools.");
+    System.out.println();
+    System.out.println("  --measureMemory: measure the number of allocations done and the amount of");
+    System.out.println("        memory used by invocations of the benchmark.");
+    System.out.println("        Default: off");
+    System.out.println();
+    System.out.println("  --vm <vm>: executable to test benchmark on. Multiple VMs may be passed");
+    System.out.println("        in as a list separated by the delimiter specified in the");
+    System.out.println("        --delimiter argument.");
+    System.out.println();
+    System.out.println("  --timeUnit <unit>: unit of time to use for result. Depends on the units");
+    System.out.println("        defined in the benchmark's getTimeUnitNames() method, if defined.");
+    System.out.println("        Default Options: ns, us, ms, s");
+    System.out.println();
+    System.out.println("  --instanceUnit <unit>: unit to use for allocation instances result.");
+    System.out.println("        Depends on the units defined in the benchmark's");
+    System.out.println("        getInstanceUnitNames() method, if defined.");
+    System.out.println("        Default Options: instances, K instances, M instances, B instances");
+    System.out.println();
+    System.out.println("  --memoryUnit <unit>: unit to use for allocation memory size result.");
+    System.out.println("        Depends on the units defined in the benchmark's");
+    System.out.println("        getMemoryUnitNames() method, if defined.");
+    System.out.println("        Default Options: B, KB, MB, GB");
+    System.out.println();
+    System.out.println("  --saveResults <file/dir>: write results to this file or directory");
+    System.out.println();
+    System.out.println("  --printScore: if present, also display an aggregate score for this run,");
+    System.out.println("        where higher is better. This number has no particular meaning,");
+    System.out.println("        but can be compared to scores from other runs that use the exact");
+    System.out.println("        same arguments.");
+    System.out.println();
+    System.out.println("  --uploadResults <file/dir>: upload this file or directory of files");
+    System.out.println("        to the web app. This argument ends Caliper early and is thus");
+    System.out.println("        incompatible with all other arguments.");
+    System.out.println();
+    System.out.println("  --debug: run without measurement for use with debugger or profiling.");
+    System.out.println();
+    System.out.println("  --debug-reps: fixed number of reps to run with --debug.");
+    System.out.println("        Default: \"" + defaultDebugReps + "\"");
+
+    // adding new options? don't forget to update executeForked()
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Benchmark.java b/caliper/src/main/java/com/google/caliper/Benchmark.java
new file mode 100644
index 0000000..06bda82
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Benchmark.java
@@ -0,0 +1,55 @@
+/*
+ * 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.Map;
+import java.util.Set;
+
+/**
+ * A collection of benchmarks that share a set of configuration parameters.
+ */
+public interface Benchmark {
+
+  Set<String> parameterNames();
+
+  Set<String> parameterValues(String parameterName);
+
+  ConfiguredBenchmark createBenchmark(Map<String, String> parameterValues);
+
+  /**
+   * A mapping of units to their values. Their values must be integers, but all values are relative,
+   * so if one unit is 1.5 times the size of another, then these units can be expressed as
+   * {"unit1"=10,"unit2"=15}. The smallest unit given by the function will be used to display
+   * immediate results when running at the command line.
+   *
+   * e.g. 0% Scenario{...} 16.08<SMALLEST-UNIT>; σ=1.72<SMALLEST-UNIT> @ 3 trials
+   */
+  Map<String, Integer> getTimeUnitNames();
+
+  Map<String, Integer> getInstanceUnitNames();
+
+  Map<String, Integer> getMemoryUnitNames();
+
+  /**
+   * Converts nanoseconds to the smallest unit defined in {@link #getTimeUnitNames()}.
+   */
+  double nanosToUnits(double nanos);
+
+  double instancesToUnits(long instances);
+
+  double bytesToUnits(long bytes);
+}
\ No newline at end of file
diff --git a/caliper/src/main/java/com/google/caliper/CaliperRc.java b/caliper/src/main/java/com/google/caliper/CaliperRc.java
new file mode 100644
index 0000000..21a283f
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/CaliperRc.java
@@ -0,0 +1,63 @@
+/*
+ * 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public class CaliperRc {
+  public static final CaliperRc INSTANCE = new CaliperRc();
+
+  private final Properties properties = new Properties();
+
+  private CaliperRc() {
+    try {
+      String caliperRcEnvVar = System.getenv("CALIPERRC");
+      File caliperRcFile = (caliperRcEnvVar == null)
+          ? new File(System.getProperty("user.home"), ".caliperrc")
+          : new File(caliperRcEnvVar);
+      if (caliperRcFile.exists()) {
+        InputStream in = new FileInputStream(caliperRcFile);
+        properties.load(in);
+        in.close();
+      } else {
+        // create it with a template
+      }
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public String getApiKey() {
+    return properties.getProperty("apiKey");
+  }
+
+  public String getPostUrl() {
+    return properties.getProperty("postUrl");
+  }
+
+  /**
+   * The HTTP proxy host name and port number separated by a colon, such as
+   * foo.com:8080
+   */
+  public String getProxy() {
+    return properties.getProperty("proxy");
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/ConfigurationException.java b/caliper/src/main/java/com/google/caliper/ConfigurationException.java
new file mode 100644
index 0000000..c4a35ec
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ConfigurationException.java
@@ -0,0 +1,33 @@
+/*
+ * 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 configuration error.
+ */
+final class ConfigurationException extends RuntimeException {
+
+  ConfigurationException(String s) {
+    super(s);
+  }
+
+  ConfigurationException(Throwable cause) {
+    super(cause);
+  }
+
+  private static final long serialVersionUID = 0;
+}
diff --git a/caliper/src/main/java/com/google/caliper/ConfiguredBenchmark.java b/caliper/src/main/java/com/google/caliper/ConfiguredBenchmark.java
new file mode 100644
index 0000000..f150ec1
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ConfiguredBenchmark.java
@@ -0,0 +1,67 @@
+/**
+ * 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.Map;
+
+public abstract class ConfiguredBenchmark {
+
+  private final Benchmark underlyingBenchmark;
+
+  protected ConfiguredBenchmark(Benchmark underlyingBenchmark) {
+    this.underlyingBenchmark = underlyingBenchmark;
+  }
+
+  /**
+   * Runs the benchmark through {@code reps} iterations.
+   *
+   * @return any object or null. Benchmark implementors may keep an accumulating
+   *      value to prevent the runtime from optimizing away the code under test.
+   *      Such an accumulator value can be returned here.
+   */
+  public abstract Object run(int reps) throws Exception;
+
+  public abstract void close() throws Exception;
+
+  public final Benchmark getBenchmark() {
+    return underlyingBenchmark;
+  }
+
+  public final double nanosToUnits(double nanos) {
+    return underlyingBenchmark.nanosToUnits(nanos);
+  }
+
+  public final Map<String, Integer> timeUnitNames() {
+    return underlyingBenchmark.getTimeUnitNames();
+  }
+
+  public final double instancesToUnits(long instances) {
+    return underlyingBenchmark.instancesToUnits(instances);
+  }
+
+  public final Map<String, Integer> instanceUnitNames() {
+    return underlyingBenchmark.getInstanceUnitNames();
+  }
+
+  public final double bytesToUnits(long bytes) {
+    return underlyingBenchmark.bytesToUnits(bytes);
+  }
+
+  public final Map<String, Integer> memoryUnitNames() {
+    return underlyingBenchmark.getMemoryUnitNames();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/ConsoleReport.java b/caliper/src/main/java/com/google/caliper/ConsoleReport.java
new file mode 100644
index 0000000..8449bda
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ConsoleReport.java
@@ -0,0 +1,427 @@
+/**
+ * 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.util.LinearTranslation;
+import com.google.common.base.Strings;
+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.Arrays;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Prints a report containing the tested values and the corresponding
+ * measurements. Measurements are grouped by variable using indentation.
+ * Alongside numeric values, quick-glance ascii art bar charts are printed.
+ * Sample output (this may not represent the exact form that is produced):
+ * <pre>
+ *              benchmark          d     ns linear runtime
+ * ConcatenationBenchmark 3.14159265   4397 ========================
+ * ConcatenationBenchmark       -0.0    223 ===============
+ *     FormatterBenchmark 3.14159265  33999 ==============================
+ *     FormatterBenchmark       -0.0  26399 =============================
+ * </pre>
+ */
+final class ConsoleReport {
+
+  private static final int barGraphWidth = 30;
+
+  private static final int UNITS_FOR_SCORE_100 = 1;
+  private static final int UNITS_FOR_SCORE_10 = 1000000000; // 1 s
+
+  private static final LinearTranslation scoreTranslation =
+      new LinearTranslation(Math.log(UNITS_FOR_SCORE_10), 10,
+                            Math.log(UNITS_FOR_SCORE_100), 100);
+
+  public static final Ordering<Entry<String, Integer>> UNIT_ORDERING =
+      new Ordering<Entry<String, Integer>>() {
+        @Override public int compare(Entry<String, Integer> a, Entry<String, Integer> b) {
+          return a.getValue().compareTo(b.getValue());
+        }
+      };
+
+  private final List<Variable> variables;
+  private final Run run;
+  private final List<Scenario> scenarios;
+
+  private final List<MeasurementType> orderedMeasurementTypes;
+  private final MeasurementType type;
+  private final double maxValue;
+  private final double logMinValue;
+  private final double logMaxValue;
+  private final EnumMap<MeasurementType, Integer> decimalDigitsMap =
+      new EnumMap<MeasurementType, Integer>(MeasurementType.class);
+  private final EnumMap<MeasurementType, Double> divideByMap =
+      new EnumMap<MeasurementType, Double>(MeasurementType.class);
+  private final EnumMap<MeasurementType, String> unitMap =
+      new EnumMap<MeasurementType, String>(MeasurementType.class);
+  private final EnumMap<MeasurementType, Integer> measurementColumnLengthMap =
+      new EnumMap<MeasurementType, Integer>(MeasurementType.class);
+  private boolean printScore;
+
+  ConsoleReport(Run run, Arguments arguments) {
+    this.run = run;
+    unitMap.put(MeasurementType.TIME, arguments.getTimeUnit());
+    unitMap.put(MeasurementType.INSTANCE, arguments.getInstanceUnit());
+    unitMap.put(MeasurementType.MEMORY, arguments.getMemoryUnit());
+
+    if (arguments.getMeasureMemory()) {
+      orderedMeasurementTypes = Arrays.asList(
+          MeasurementType.TIME, MeasurementType.INSTANCE, MeasurementType.MEMORY);
+    } else {
+      orderedMeasurementTypes = Arrays.asList(MeasurementType.TIME);
+    }
+
+    if (arguments.getPrimaryMeasurementType() != null) {
+      this.type = arguments.getPrimaryMeasurementType();
+    } else {
+      this.type = MeasurementType.TIME;
+    }
+
+    double min = Double.POSITIVE_INFINITY;
+    double max = 0;
+
+    Multimap<String, String> nameToValues = LinkedHashMultimap.create();
+    List<Variable> variablesBuilder = new ArrayList<Variable>();
+    for (Entry<Scenario, ScenarioResult> entry : this.run.getMeasurements().entrySet()) {
+      Scenario scenario = entry.getKey();
+      double d = entry.getValue().getMeasurementSet(type).medianUnits();
+
+      min = Math.min(min, d);
+      max = Math.max(max, d);
+
+      for (Entry<String, String> variable : scenario.getVariables().entrySet()) {
+        String name = variable.getKey();
+        nameToValues.put(name, variable.getValue());
+      }
+    }
+
+    for (Entry<String, Collection<String>> entry : nameToValues.asMap().entrySet()) {
+      Variable variable = new Variable(entry.getKey(), entry.getValue());
+      variablesBuilder.add(variable);
+    }
+
+    /*
+     * 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 variable's collection of sums. Higher standard
+     * deviation implies higher influence on the measured result.
+     */
+    double sumOfAllMeasurements = 0;
+    for (ScenarioResult measurement : this.run.getMeasurements().values()) {
+      sumOfAllMeasurements += measurement.getMeasurementSet(type).medianUnits();
+    }
+    for (Variable variable : variablesBuilder) {
+      int numValues = variable.values.size();
+      double[] sumForValue = new double[numValues];
+      for (Entry<Scenario, ScenarioResult> entry
+          : this.run.getMeasurements().entrySet()) {
+        Scenario scenario = entry.getKey();
+        sumForValue[variable.index(scenario)] +=
+            entry.getValue().getMeasurementSet(type).medianUnits();
+      }
+      double mean = sumOfAllMeasurements / sumForValue.length;
+      double stdDeviationSquared = 0;
+      for (double value : sumForValue) {
+        double distance = value - mean;
+        stdDeviationSquared += distance * distance;
+      }
+      variable.stdDeviation = Math.sqrt(stdDeviationSquared / numValues);
+    }
+
+    this.variables = new StandardDeviationOrdering().reverse().sortedCopy(variablesBuilder);
+    this.scenarios = new ByVariablesOrdering().sortedCopy(this.run.getMeasurements().keySet());
+    this.maxValue = max;
+    this.logMinValue = Math.log(min);
+    this.logMaxValue = Math.log(max);
+
+    EnumMap<MeasurementType, Integer> digitsBeforeDecimalMap =
+      new EnumMap<MeasurementType, Integer>(MeasurementType.class);
+    EnumMap<MeasurementType, Integer> decimalPointMap =
+      new EnumMap<MeasurementType, Integer>(MeasurementType.class);
+    for (MeasurementType measurementType : orderedMeasurementTypes) {
+      double maxForType = 0;
+      double minForType = Double.POSITIVE_INFINITY;
+      for (Entry<Scenario, ScenarioResult> entry : this.run.getMeasurements().entrySet()) {
+        double d = entry.getValue().getMeasurementSet(measurementType).medianUnits();
+        minForType = Math.min(minForType, d);
+        maxForType = Math.max(maxForType, d);
+      }
+
+      unitMap.put(measurementType,
+          getUnit(unitMap.get(measurementType), measurementType, minForType));
+
+      divideByMap.put(measurementType,
+          (double) getUnits(measurementType).get(unitMap.get(measurementType)));
+
+      int numDigitsInMin = ceil(Math.log10(minForType));
+      decimalDigitsMap.put(measurementType,
+          ceil(Math.max(0, ceil(Math.log10(divideByMap.get(measurementType))) + 3 - numDigitsInMin)));
+
+      digitsBeforeDecimalMap.put(measurementType,
+          Math.max(1, ceil(Math.log10(maxForType / divideByMap.get(measurementType)))));
+
+      decimalPointMap.put(measurementType, decimalDigitsMap.get(measurementType) > 0 ? 1 : 0);
+
+      measurementColumnLengthMap.put(measurementType, Math.max(maxForType > 0
+          ?  digitsBeforeDecimalMap.get(measurementType) + decimalPointMap.get(measurementType)
+              + decimalDigitsMap.get(measurementType)
+          : 1, unitMap.get(measurementType).trim().length()));
+    }
+
+    this.printScore = arguments.printScore();
+  }
+
+  private String getUnit(String userSuppliedUnit, MeasurementType measurementType, double min) {
+    Map<String, Integer> units = getUnits(measurementType);
+
+    if (userSuppliedUnit == null) {
+      List<Entry<String, Integer>> entries = UNIT_ORDERING.reverse().sortedCopy(units.entrySet());
+      for (Entry<String, Integer> entry : entries) {
+        if (min / entry.getValue() >= 1) {
+          return entry.getKey();
+        }
+      }
+      // if no unit works, just use the smallest available unit.
+      return entries.get(entries.size() - 1).getKey();
+    }
+
+    if (!units.keySet().contains(userSuppliedUnit)) {
+      throw new RuntimeException("\"" + unitMap.get(measurementType) + "\" is not a valid unit.");
+    }
+    return userSuppliedUnit;
+  }
+
+  private Map<String, Integer> getUnits(MeasurementType measurementType) {
+    Map<String, Integer> units = null;
+    for (Entry<Scenario, ScenarioResult> entry : run.getMeasurements().entrySet()) {
+      if (units == null) {
+        units = entry.getValue().getMeasurementSet(measurementType).getUnitNames();
+      } else {
+        if (!units.equals(entry.getValue().getMeasurementSet(measurementType).getUnitNames())) {
+          throw new RuntimeException("measurement sets for run contain multiple, incompatible unit"
+              + " sets.");
+        }
+      }
+    }
+    if (units == null) {
+      throw new RuntimeException("run has no measurements.");
+    }
+    if (units.isEmpty()) {
+      throw new RuntimeException("no units specified.");
+    }
+    return units;
+  }
+
+  /**
+   * A variable and the set of values to which it has been assigned.
+   */
+  private static class Variable {
+    final String name;
+    final ImmutableList<String> values;
+    final int maxLength;
+    double stdDeviation;
+
+    Variable(String name, Collection<String> values) {
+      this.name = name;
+      this.values = ImmutableList.copyOf(values);
+
+      int maxLen = name.length();
+      for (String value : values) {
+        maxLen = Math.max(maxLen, value.length());
+      }
+      this.maxLength = maxLen;
+    }
+
+    String get(Scenario scenario) {
+      return scenario.getVariables().get(name);
+    }
+
+    int index(Scenario scenario) {
+      return values.indexOf(get(scenario));
+    }
+
+    boolean isInteresting() {
+      return values.size() > 1;
+    }
+  }
+
+  /**
+   * Orders the different variables by their standard deviation. This results
+   * in an appropriate grouping of output values.
+   */
+  private static class StandardDeviationOrdering extends Ordering<Variable> {
+    public int compare(Variable a, Variable b) {
+      return Double.compare(a.stdDeviation, b.stdDeviation);
+    }
+  }
+
+  /**
+   * Orders scenarios by the variables.
+   */
+  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;
+        }
+      }
+      return 0;
+    }
+  }
+
+  void displayResults() {
+    printValues();
+    System.out.println();
+    printUninterestingVariables();
+    printCharCounts();
+  }
+
+  private void printCharCounts() {
+    int systemOutCharCount = 0;
+    int systemErrCharCount = 0;
+    for (ScenarioResult scenarioResult : run.getMeasurements().values()) {
+      for (MeasurementType measurementType : MeasurementType.values()) {
+        MeasurementSet measurementSet = scenarioResult.getMeasurementSet(measurementType);
+        if (measurementSet != null) {
+          systemOutCharCount += measurementSet.getSystemOutCharCount();
+          systemErrCharCount += measurementSet.getSystemErrCharCount();
+        }
+      }
+    }
+    if (systemOutCharCount > 0 || systemErrCharCount > 0) {
+      System.out.println();
+      System.out.println("Note: benchmarks printed " + systemOutCharCount
+          + " characters to System.out and " + systemErrCharCount + " characters to System.err."
+          + " Use --debug to see this output.");
+    }
+  }
+
+  /**
+   * Prints a table of values.
+   */
+  private void printValues() {
+    // header
+    for (Variable variable : variables) {
+      if (variable.isInteresting()) {
+        System.out.printf("%" + variable.maxLength + "s ", variable.name);
+      }
+    }
+    // doesn't make sense to show graphs at all for 1
+    // scenario, since it leads to vacuous graphs.
+    boolean showGraphs = scenarios.size() > 1;
+
+    EnumMap<MeasurementType, String> numbersFormatMap =
+        new EnumMap<MeasurementType, String>(MeasurementType.class);
+    for (MeasurementType measurementType : orderedMeasurementTypes) {
+      if (measurementType != type) {
+        System.out.printf("%" + measurementColumnLengthMap.get(measurementType) + "s ",
+            unitMap.get(measurementType).trim());
+      }
+
+      numbersFormatMap.put(measurementType,
+          "%" + measurementColumnLengthMap.get(measurementType)
+              + "." + decimalDigitsMap.get(measurementType) + "f"
+              + (type == measurementType ? "" : " "));
+    }
+
+    System.out.printf("%" + measurementColumnLengthMap.get(type) + "s", unitMap.get(type).trim());
+    if (showGraphs) {
+      System.out.print(" linear runtime");
+    }
+    System.out.println();
+
+    double sumOfLogs = 0.0;
+
+    for (Scenario scenario : scenarios) {
+      for (Variable variable : variables) {
+        if (variable.isInteresting()) {
+          System.out.printf("%" + variable.maxLength + "s ", variable.get(scenario));
+        }
+      }
+      ScenarioResult measurement = run.getMeasurements().get(scenario);
+      sumOfLogs += Math.log(measurement.getMeasurementSet(type).medianUnits());
+
+      for (MeasurementType measurementType : orderedMeasurementTypes) {
+        if (measurementType != type) {
+          System.out.printf(numbersFormatMap.get(measurementType),
+              measurement.getMeasurementSet(measurementType).medianUnits() / divideByMap.get(measurementType));
+        }
+      }
+
+      System.out.printf(numbersFormatMap.get(type),
+          measurement.getMeasurementSet(type).medianUnits() / divideByMap.get(type));
+      if (showGraphs) {
+        System.out.printf(" %s", barGraph(measurement.getMeasurementSet(type).medianUnits()));
+      }
+      System.out.println();
+    }
+
+    if (printScore) {
+      // arithmetic mean of logs, aka log of geometric mean
+      double meanLogUnits = sumOfLogs / scenarios.size();
+      System.out.format("%nScore: %.3f%n", scoreTranslation.translate(meanLogUnits));
+    }
+  }
+
+  /**
+   * Prints variables with only one unique value.
+   */
+  private void printUninterestingVariables() {
+    for (Variable variable : variables) {
+      if (!variable.isInteresting()) {
+        System.out.println(variable.name + ": " + Iterables.getOnlyElement(variable.values));
+      }
+    }
+  }
+
+  /**
+   * Returns a string containing a bar of proportional width to the specified
+   * value.
+   */
+  private String barGraph(double value) {
+    int graphLength = floor(value / maxValue * barGraphWidth);
+    graphLength = Math.max(1, graphLength);
+    graphLength = Math.min(barGraphWidth, graphLength);
+    return Strings.repeat("=", graphLength);
+  }
+
+  @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/caliper/src/main/java/com/google/caliper/CountingPrintStream.java b/caliper/src/main/java/com/google/caliper/CountingPrintStream.java
new file mode 100644
index 0000000..5c19a61
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/CountingPrintStream.java
@@ -0,0 +1,77 @@
+/*
+ * 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 java.io.PrintStream;
+
+/**
+ * Counts how many characters were written.
+ */
+final class CountingPrintStream extends PrintStream {
+
+  private final PrintStream delegate;
+  private int count;
+
+  CountingPrintStream(PrintStream delegate) {
+    super(delegate);
+    this.delegate = delegate;
+  }
+
+  public int getCount() {
+    return count;
+  }
+
+  @Override public void flush() {
+    delegate.flush();
+  }
+
+  @Override public void close() {
+    delegate.close();
+  }
+
+  @Override public boolean checkError() {
+    return delegate.checkError();
+  }
+
+  @Override protected void setError() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override protected void clearError() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override public void write(int b) {
+    count++;
+    delegate.write(b);
+  }
+
+  @Override public void write(byte[] buffer, int offset, int length) {
+    count += length;
+    delegate.write(buffer, offset, length);
+  }
+
+  @Override public void print(char[] chars) {
+    count += chars.length;
+    delegate.print(chars);
+  }
+
+  @Override public void print(String s) {
+    count += s.length();
+    delegate.print(s);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/DalvikVm.java b/caliper/src/main/java/com/google/caliper/DalvikVm.java
new file mode 100644
index 0000000..f89cc28
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/DalvikVm.java
@@ -0,0 +1,62 @@
+/*
+ * 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.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The dalvikvm run on Android devices via the app_process executable.
+ */
+final class DalvikVm extends Vm {
+
+  public static boolean isDalvikVm() {
+    return "Dalvik".equals(System.getProperty("java.vm.name"));
+  }
+
+  public static String vmName() {
+    return "app_process";
+  }
+
+  @Override public List<String> getVmSpecificOptions(MeasurementType type, Arguments arguments) {
+    if (!arguments.getCaptureVmLog()) {
+      return ImmutableList.of();
+    }
+
+    List<String> result = new ArrayList<String>();
+    if (arguments.getCaptureVmLog()) {
+      // TODO: currently GC goes to logcat.
+      // result.add("-verbose:gc");
+    }
+    return result;
+  }
+
+  @Override public ProcessBuilder newProcessBuilder(File workingDirectory, String classPath,
+      ImmutableList<String> vmArgs, String className, ImmutableList<String> applicationArgs) {
+    ProcessBuilder result = new ProcessBuilder();
+    result.directory(workingDirectory);
+    result.command().addAll(vmArgs);
+    result.command().add("-Djava.class.path=" + classPath);
+    result.command().add(workingDirectory.getPath());
+    result.command().add(className);
+    result.command().addAll(applicationArgs);
+    return result;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/DebugMeasurer.java b/caliper/src/main/java/com/google/caliper/DebugMeasurer.java
new file mode 100644
index 0000000..64d4b7f
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/DebugMeasurer.java
@@ -0,0 +1,42 @@
+/*
+ * 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 static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Supplier;
+
+/**
+ * Measure's the benchmark's per-trial execution time.
+ */
+class DebugMeasurer extends Measurer {
+
+  private final int reps;
+
+  DebugMeasurer(int reps) {
+    checkArgument(reps > 0);
+    this.reps = reps;
+  }
+
+  @Override public MeasurementSet run(Supplier<ConfiguredBenchmark> testSupplier)
+      throws Exception {
+    ConfiguredBenchmark benchmark = testSupplier.get();
+    benchmark.run(reps);
+    benchmark.close();
+    return null;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Environment.java b/caliper/src/main/java/com/google/caliper/Environment.java
new file mode 100644
index 0000000..75c2813
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Environment.java
@@ -0,0 +1,63 @@
+/*
+ * 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.common.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A description of an environment in which benchmarks are run.
+ *
+ * WARNING: a JSON representation of this class is stored on the app engine server. If any changes
+ * are made to this class, a deserialization adapter must be written for this class to ensure
+ * backwards compatibility.
+ *
+ * <p>Gwt-safe
+ */
+@SuppressWarnings("serial")
+@GwtCompatible
+public final class Environment
+    implements Serializable /* for GWT Serialization */ {
+  private /*final*/ Map<String, String> propertyMap;
+
+  public Environment(Map<String, String> propertyMap) {
+    this.propertyMap = new HashMap<String, String>(propertyMap);
+  }
+
+  public Map<String, String> getProperties() {
+    return propertyMap;
+  }
+
+  @Override public boolean equals(Object o) {
+    return o instanceof Environment
+        && ((Environment) o).propertyMap.equals(propertyMap);
+  }
+
+  @Override public int hashCode() {
+    return propertyMap.hashCode();
+  }
+
+  @Override public String toString() {
+    return propertyMap.toString();
+  }
+
+  @SuppressWarnings("unused")
+  private Environment() {} // for GWT Serialization
+}
diff --git a/caliper/src/main/java/com/google/caliper/EnvironmentGetter.java b/caliper/src/main/java/com/google/caliper/EnvironmentGetter.java
new file mode 100644
index 0000000..9b7150b
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/EnvironmentGetter.java
@@ -0,0 +1,178 @@
+/*
+ * 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.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.Multimap;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class EnvironmentGetter {
+
+  public Environment getEnvironmentSnapshot() {
+    Map<String, String> propertyMap = new HashMap<String, String>();
+
+    @SuppressWarnings("unchecked")
+    Map<String, String> sysProps = (Map<String, String>) (Map) System.getProperties();
+
+    // Sometimes java.runtime.version is more descriptive than java.version
+    String version = sysProps.get("java.version");
+    String alternateVersion = sysProps.get("java.runtime.version");
+    if (alternateVersion != null && alternateVersion.length() > version.length()) {
+      version = alternateVersion;
+    }
+    propertyMap.put("jre.version", version);
+
+    propertyMap.put("jre.vmname", sysProps.get("java.vm.name"));
+    propertyMap.put("jre.vmversion", sysProps.get("java.vm.version"));
+    propertyMap.put("jre.availableProcessors",
+        Integer.toString(Runtime.getRuntime().availableProcessors()));
+
+    String osName = sysProps.get("os.name");
+    propertyMap.put("os.name", osName);
+    propertyMap.put("os.version", sysProps.get("os.version"));
+    propertyMap.put("os.arch", sysProps.get("os.arch"));
+
+    try {
+      propertyMap.put("host.name", InetAddress.getLocalHost().getHostName());
+    } catch (UnknownHostException ignored) {
+    }
+
+    if (osName.equals("Linux")) {
+      getLinuxEnvironment(propertyMap);
+    }
+
+    return new Environment(propertyMap);
+  }
+
+  private void getLinuxEnvironment(Map<String, String> propertyMap) {
+    // the following probably doesn't work on ALL linux
+    Multimap<String, String> cpuInfo = propertiesFromLinuxFile("/proc/cpuinfo");
+    propertyMap.put("host.cpus", Integer.toString(cpuInfo.get("processor").size()));
+    String s = "cpu cores";
+    propertyMap.put("host.cpu.cores", describe(cpuInfo, s));
+    propertyMap.put("host.cpu.names", describe(cpuInfo, "model name"));
+    propertyMap.put("host.cpu.cachesize", describe(cpuInfo, "cache size"));
+
+    Multimap<String, String> memInfo = propertiesFromLinuxFile("/proc/meminfo");
+    // TODO redo memInfo.toString() so we don't get square brackets
+    propertyMap.put("host.memory.physical", memInfo.get("MemTotal").toString());
+    propertyMap.put("host.memory.swap", memInfo.get("SwapTotal").toString());
+
+    getAndroidEnvironment(propertyMap);
+  }
+
+  private void getAndroidEnvironment(Map<String, String> propertyMap) {
+    try {
+      Map<String, String> map = getAndroidProperties();
+      String manufacturer = map.get("ro.product.manufacturer");
+      String device = map.get("ro.product.device");
+      propertyMap.put("android.device", manufacturer + " " + device); // "Motorola sholes"
+
+      String brand = map.get("ro.product.brand");
+      String model = map.get("ro.product.model");
+      propertyMap.put("android.model", brand + " " + model); // "verizon Droid"
+
+      String release = map.get("ro.build.version.release");
+      String id = map.get("ro.build.id");
+      propertyMap.put("android.release", release + " " + id); // "Gingerbread GRH07B"
+    } catch (IOException ignored) {
+    }
+  }
+
+  private static String describe(Multimap<String, String> cpuInfo, String s) {
+    Collection<String> strings = cpuInfo.get(s);
+    // TODO redo the ImmutableMultiset.toString() call so we don't get square brackets
+    return (strings.size() == 1)
+        ? strings.iterator().next()
+        : ImmutableMultiset.copyOf(strings).toString();
+  }
+
+  /**
+   * Returns the key/value pairs from the specified properties-file like
+   * reader. Unlike standard Java properties files, {@code reader} is allowed
+   * to list the same property multiple times. Comments etc. are unsupported.
+   */
+  private static Multimap<String, String> propertiesFileToMultimap(Reader reader)
+      throws IOException {
+    ImmutableMultimap.Builder<String, String> result = ImmutableMultimap.builder();
+    BufferedReader in = new BufferedReader(reader);
+
+    String line;
+    while((line = in.readLine()) != null) {
+      String[] parts = line.split("\\s*\\:\\s*", 2);
+      if (parts.length == 2) {
+        result.put(parts[0], parts[1]);
+      }
+    }
+    in.close();
+
+    return result.build();
+  }
+
+  private static Multimap<String, String> propertiesFromLinuxFile(String file) {
+    try {
+      Process process = Runtime.getRuntime().exec(new String[]{"/bin/cat", file});
+      return propertiesFileToMultimap(
+          new InputStreamReader(process.getInputStream(), "ISO-8859-1"));
+    } catch (IOException e) {
+      return ImmutableMultimap.of();
+    }
+  }
+
+  public static void main(String[] args) {
+    Environment snapshot = new EnvironmentGetter().getEnvironmentSnapshot();
+    for (Map.Entry<String, String> entry : snapshot.getProperties().entrySet()) {
+      System.out.println(entry.getKey() + " " + entry.getValue());
+    }
+  }
+
+  /**
+   * Android properties are available from adb shell /system/bin/getprop. That
+   * program prints Android system properties in this format:
+   * [ro.product.model]: [Droid]
+   * [ro.product.brand]: [verizon]
+   */
+  private static Map<String, String> getAndroidProperties() throws IOException {
+    Map<String, String> result = new HashMap<String, String>();
+
+    Process process = Runtime.getRuntime().exec(new String[] {"/system/bin/getprop"});
+    BufferedReader reader = new BufferedReader(
+        new InputStreamReader(process.getInputStream(), "ISO-8859-1"));
+
+    Pattern pattern = Pattern.compile("\\[([^\\]]*)\\]: \\[([^\\]]*)\\]");
+    String line;
+    while ((line = reader.readLine()) != null) {
+      Matcher matcher = pattern.matcher(line);
+      if (matcher.matches()) {
+        result.put(matcher.group(1), matcher.group(2));
+      }
+    }
+    return result;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/InProcessRunner.java b/caliper/src/main/java/com/google/caliper/InProcessRunner.java
new file mode 100644
index 0000000..1ef4788
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/InProcessRunner.java
@@ -0,0 +1,117 @@
+/*
+ * 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.ExceptionFromUserCodeException;
+import com.google.common.base.Supplier;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.List;
+
+/**
+ * Executes a benchmark in the current VM.
+ */
+final class InProcessRunner {
+
+  public void run(String... args) {
+    Arguments arguments = Arguments.parse(args);
+
+    ScenarioSelection scenarioSelection = new ScenarioSelection(arguments);
+
+    try {
+      Measurer measurer = getMeasurer(arguments);
+      List<Scenario> scenarios = scenarioSelection.select();
+      // We only expect one scenario right now - if we have more, something has gone wrong.
+      // This matters for things like reading the measurements. This is only done once, so if
+      // multiple scenarios are executed, they will be ignored!
+      if (scenarios.size() != 1) {
+        throw new IllegalArgumentException("Invalid arguments to subprocess. Expected exactly one "
+            + "scenario but got " + scenarios.size());
+      }
+      Scenario scenario = scenarios.get(0);
+
+      System.out.println("starting " + scenario);
+      MeasurementSet measurementSet = run(scenarioSelection, scenario, measurer);
+      System.out.println(arguments.getMarker() + Json.measurementSetToJson(measurementSet));
+    } catch (UserException e) {
+      throw e;
+    } catch (Exception e) {
+      throw new ExceptionFromUserCodeException(e);
+    }
+  }
+
+  public MeasurementSet run(final ScenarioSelection scenarioSelection, final Scenario scenario,
+      Measurer measurer) throws Exception {
+    Supplier<ConfiguredBenchmark> supplier = new Supplier<ConfiguredBenchmark>() {
+      @Override public ConfiguredBenchmark get() {
+        return scenarioSelection.createBenchmark(scenario);
+      }
+    };
+
+    PrintStream out = System.out;
+    PrintStream err = System.err;
+    measurer.setLogStream(out);
+    CountingPrintStream countedOut = new CountingPrintStream(out);
+    CountingPrintStream countedErr = new CountingPrintStream(err);
+    System.setOut(countedOut);
+    System.setErr(countedErr);
+    try {
+      MeasurementSet measurementSet = measurer.run(supplier);
+      if (measurementSet != null) {
+        measurementSet = measurementSet.plusCharCounts(
+            countedOut.getCount(), countedErr.getCount());
+      }
+      return measurementSet;
+    } finally {
+      System.setOut(out);
+      System.setErr(err);
+    }
+  }
+
+  private Measurer getMeasurer(Arguments arguments) {
+    if (arguments.getMeasurementType() == MeasurementType.TIME) {
+      return new TimeMeasurer(arguments.getWarmupMillis(), arguments.getRunMillis());
+    } else if (arguments.getMeasurementType() == MeasurementType.INSTANCE) {
+      return new InstancesAllocationMeasurer();
+    } else if (arguments.getMeasurementType() == MeasurementType.MEMORY) {
+      return new MemoryAllocationMeasurer();
+    } else if (arguments.getMeasurementType() == MeasurementType.DEBUG) {
+      return new DebugMeasurer(arguments.getDebugReps());
+    } else {
+      throw new IllegalArgumentException("unrecognized measurement type: "
+          + arguments.getMeasurementType());
+    }
+  }
+
+  public static void main(String... args) throws Exception {
+    try {
+      new InProcessRunner().run(args);
+      System.exit(0); // user code may have leave non-daemon threads behind!
+    } catch (UserException e) {
+      e.display(); // TODO: send this to the host process
+      System.out.println(LogConstants.CALIPER_LOG_PREFIX + LogConstants.SCENARIOS_FINISHED);
+      System.exit(1);
+    }
+  }
+
+  public PrintStream nullPrintStream() {
+    return new PrintStream(new OutputStream() {
+      public void write(int b) {}
+    });
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/InstancesAllocationMeasurer.java b/caliper/src/main/java/com/google/caliper/InstancesAllocationMeasurer.java
new file mode 100644
index 0000000..3fe89c7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/InstancesAllocationMeasurer.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+public final class InstancesAllocationMeasurer extends AllocationMeasurer {
+
+  InstancesAllocationMeasurer() {
+    type = "instance";
+  }
+
+  @Override protected long incrementAllocationCount(long oldAllocationCount, int arrayCount,
+      long size) {
+    return oldAllocationCount + 1;
+  }
+
+  @Override protected Measurement getMeasurement(ConfiguredBenchmark benchmark, long allocations) {
+    return new Measurement(benchmark.instanceUnitNames(), allocations,
+        benchmark.instancesToUnits(allocations));
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Json.java b/caliper/src/main/java/com/google/caliper/Json.java
new file mode 100644
index 0000000..9e3f809
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Json.java
@@ -0,0 +1,226 @@
+/*
+ * 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.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.reflect.TypeToken;
+
+import java.lang.reflect.Type;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * Ordinarily serialization should be done within the class that is being serialized. However,
+ * many of these classes are used by GWT, which dies when it sees Gson.
+ */
+public final class Json {
+  /**
+   * This Gson instance must be used when serializing a class that includes a Run as a member
+   * or as a member of a member (etc.), otherwise the Map<Scenario, ScenarioResult> will not
+   * be correctly serialized.
+   */
+  private static final Gson GSON_INSTANCE =
+      new GsonBuilder()
+          .registerTypeAdapter(Date.class, new DateTypeAdapter())
+          .registerTypeAdapter(Run.class, new RunTypeAdapter())
+          .registerTypeAdapter(Measurement.class, new MeasurementDeserializer())
+          .create();
+
+  public static Gson getGsonInstance() {
+    return GSON_INSTANCE;
+  }
+
+  public static String measurementSetToJson(MeasurementSet measurementSet) {
+    return new Gson().toJson(measurementSet);
+  }
+
+  /**
+   * Attempts to extract a MeasurementSet from a string, assuming it is JSON. If this fails, it
+   * tries to extract it from the string assuming it is a space-separated list of double values.
+   */
+  public static MeasurementSet measurementSetFromJson(String measurementSetJson) {
+    try {
+      return getGsonInstance().fromJson(measurementSetJson, MeasurementSet.class);
+    } catch (JsonParseException e) {
+      // might be an old MeasurementSet, so fall back on failure to the old, space separated
+      // serialization method.
+      try {
+        String[] measurementStrings = measurementSetJson.split("\\s+");
+        List<Measurement> measurements = new ArrayList<Measurement>();
+        for (String s : measurementStrings) {
+          measurements.add(
+              new Measurement(ImmutableMap.of("ns", 1, "us", 1000, "ms", 1000000, "s", 1000000000),
+              Double.valueOf(s), Double.valueOf(s)));
+        }
+        // seconds and variations is the default unit
+        return new MeasurementSet(measurements.toArray(new Measurement[measurements.size()]));
+      } catch (NumberFormatException ignore) {
+        throw new IllegalArgumentException("Not a measurement set: " + measurementSetJson);
+      }
+    }
+  }
+
+  public static MeasurementSet measurementSetFromJson(JsonObject measurementSetJson) {
+    return getGsonInstance().fromJson(measurementSetJson, MeasurementSet.class);
+  }
+
+  /**
+   * Backwards compatibility!
+   */
+  private static class MeasurementDeserializer implements JsonDeserializer<Measurement> {
+    @Override public Measurement deserialize(JsonElement jsonElement, Type type,
+        JsonDeserializationContext context) throws JsonParseException {
+      JsonObject obj = jsonElement.getAsJsonObject();
+      if (obj.has("raw") && obj.has("processed")) {
+        return new Measurement(
+            context.<Map<String, Integer>>deserialize(obj.get("unitNames"),
+                new TypeToken<Map<String, Integer>>() {}.getType()),
+            context.<Double>deserialize(obj.get("raw"), Double.class),
+            context.<Double>deserialize(obj.get("processed"), Double.class));
+      }
+      if (obj.has("nanosPerRep") && obj.has("unitsPerRep") && obj.has("unitNames")) {
+        return new Measurement(
+            context.<Map<String, Integer>>deserialize(obj.get("unitNames"),
+                new TypeToken<Map<String, Integer>>() {}.getType()),
+            context.<Double>deserialize(obj.get("nanosPerRep"), Double.class),
+            context.<Double>deserialize(obj.get("unitsPerRep"), Double.class));
+      }
+      throw new JsonParseException(obj.toString());
+    }
+  }
+
+  /**
+   * This adapter is necessary because gson doesn't handle Maps more complex than Map<String, ...>
+   * in a useful way. For example, Map<Scenario, ScenarioResult>'s serialized version simply uses
+   * Scenario.toString() as the keys. This adapter stores this Map as lists of
+   * KeyValuePair<Scenario, ScenarioResult> instead, to preserve the Scenario objects on
+   * deserialization.
+   */
+  private static class RunTypeAdapter implements JsonSerializer<Run>, JsonDeserializer<Run> {
+
+    @Override public Run deserialize(JsonElement jsonElement, Type type,
+        JsonDeserializationContext context) throws JsonParseException {
+
+      List<KeyValuePair<Scenario, ScenarioResult>> mapList = context.deserialize(
+          jsonElement.getAsJsonObject().get("measurements"),
+          new TypeToken<List<KeyValuePair<Scenario, ScenarioResult>>>() {}.getType());
+      Map<Scenario, ScenarioResult> measurements = new LinkedHashMap<Scenario, ScenarioResult>();
+      for (KeyValuePair<Scenario, ScenarioResult> entry : mapList) {
+        measurements.put(entry.getKey(), entry.getValue());
+      }
+
+      String benchmarkName =
+          context.deserialize(jsonElement.getAsJsonObject().get("benchmarkName"), String.class);
+
+      Date executedTimestamp = context.deserialize(
+          jsonElement.getAsJsonObject().get("executedTimestamp"), Date.class);
+
+      return new Run(measurements, benchmarkName, executedTimestamp);
+    }
+
+    @Override public JsonElement serialize(Run run, Type type, JsonSerializationContext context) {
+      JsonObject result = new JsonObject();
+      result.add("benchmarkName", context.serialize(run.getBenchmarkName()));
+      result.add("executedTimestamp", context.serialize(run.getExecutedTimestamp()));
+
+      List<KeyValuePair<Scenario, ScenarioResult>> mapList =
+          new ArrayList<KeyValuePair<Scenario, ScenarioResult>>();
+      for (Map.Entry<Scenario, ScenarioResult> entry : run.getMeasurements().entrySet()) {
+        mapList.add(new KeyValuePair<Scenario, ScenarioResult>(entry.getKey(), entry.getValue()));
+      }
+      result.add("measurements", context.serialize(mapList,
+          new TypeToken<List<KeyValuePair<Scenario, ScenarioResult>>>() {}.getType()));
+
+      return result;
+    }
+  }
+
+  private static class DateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
+    private final DateFormat dateFormat;
+
+    private DateTypeAdapter() {
+      dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz", Locale.US);
+      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    @Override public synchronized JsonElement serialize(Date date, Type type,
+        JsonSerializationContext jsonSerializationContext) {
+      return new JsonPrimitive(dateFormat.format(date));
+    }
+
+    @Override public synchronized Date deserialize(JsonElement jsonElement, Type type,
+        JsonDeserializationContext jsonDeserializationContext) {
+      String dateString = jsonElement.getAsString();
+      // first try to parse as an ISO 8601 date
+      try {
+        return dateFormat.parse(dateString);
+      } catch (ParseException ignored) {
+      }
+      // next, try a GSON-style locale-specific dates (for Caliper r282 and earlier)
+      try {
+        return DateFormat.getDateTimeInstance().parse(dateString);
+      } catch (ParseException ignored) {
+      }
+      throw new JsonParseException(dateString);
+    }
+  }
+
+  /**
+   * This is similar to the Map.Entry class, but is necessary since Entrys are not supported
+   * by gson.
+   */
+  private static class KeyValuePair<K, V> {
+    private K k;
+    private V v;
+
+    KeyValuePair(K k, V v) {
+      this.k = k;
+      this.v = v;
+    }
+
+    public K getKey() {
+      return k;
+    }
+
+    public V getValue() {
+      return v;
+    }
+
+    @SuppressWarnings("unused")
+    private KeyValuePair() {} // for gson
+  }
+
+  private Json() {} // static class
+}
diff --git a/caliper/src/main/java/com/google/caliper/LogConstants.java b/caliper/src/main/java/com/google/caliper/LogConstants.java
new file mode 100644
index 0000000..09acebe
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/LogConstants.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+public final class LogConstants {
+  /**
+   * Must be prepended to line of XML that represents normalized scenario.
+   */
+  public static final String SCENARIO_JSON_PREFIX = "[scenario] ";
+  public static final String MEASUREMENT_JSON_PREFIX = "[measurement] ";
+
+  /**
+   * Must be prepended to any logs that are to be included in the run event log.
+   */
+  public static final String CALIPER_LOG_PREFIX = "[caliper] ";
+  public static final String SCENARIOS_STARTING = "[starting scenarios]";
+  public static final String STARTING_SCENARIO_PREFIX = "[starting scenario] ";
+  public static final String SCENARIO_FINISHED = "[scenario finished]";
+  public static final String SCENARIOS_FINISHED = "[scenarios finished]";
+
+  /**
+   * All events will be logged from when {@code MEASURED_SECTION_STARTING} is logged until
+   * {@code MEASURED_SECTION_DONE} is logged.
+   */
+  public static final String MEASURED_SECTION_STARTING = "[starting measured section]";
+  public static final String MEASURED_SECTION_DONE = "[done measured section]";
+
+  private LogConstants() {}
+}
diff --git a/caliper/src/main/java/com/google/caliper/Measurement.java b/caliper/src/main/java/com/google/caliper/Measurement.java
new file mode 100644
index 0000000..35865b6
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Measurement.java
@@ -0,0 +1,93 @@
+/*
+ * 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.common.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents a measurement of a single run of a benchmark.
+ */
+@SuppressWarnings("serial")
+@GwtCompatible
+public final class Measurement
+  implements Serializable /* for GWT */ {
+
+  public static final Comparator<Measurement> SORT_BY_NANOS = new Comparator<Measurement>() {
+    @Override public int compare(Measurement a, Measurement b) {
+      double aNanos = a.getRaw();
+      double bNanos = b.getRaw();
+      return Double.compare(aNanos, bNanos);
+    }
+  };
+
+  public static final Comparator<Measurement> SORT_BY_UNITS = new Comparator<Measurement>() {
+    @Override public int compare(Measurement a, Measurement b) {
+      double aNanos = a.getProcessed();
+      double bNanos = b.getProcessed();
+      return Double.compare(aNanos, bNanos);
+    }
+  };
+
+  private /*final*/ double raw;
+  private /*final*/ double processed;
+  private /*final*/ Map<String, Integer> unitNames;
+
+  public Measurement(Map<String, Integer> unitNames, double raw, double processed) {
+    this.unitNames = new HashMap<String, Integer>(unitNames);
+    this.raw = raw;
+    this.processed = processed;
+  }
+
+  public Map<String, Integer> getUnitNames() {
+    return new HashMap<String, Integer>(unitNames);
+  }
+
+  public double getRaw() {
+    return raw;
+  }
+
+  public double getProcessed() {
+    return processed;
+  }
+
+  @Override public boolean equals(Object o) {
+    return o instanceof Measurement
+        && ((Measurement) o).raw == raw
+        && ((Measurement) o).processed == processed
+        && ((Measurement) o).unitNames.equals(unitNames);
+  }
+
+  @Override public int hashCode() {
+    return (int) raw // Double.doubleToLongBits doesn't exist on GWT
+        + (int) processed * 37
+        + unitNames.hashCode() * 1373;
+  }
+
+  @Override public String toString() {
+    return (raw != processed
+        ? raw + "/" + processed
+        : Double.toString(raw));
+  }
+
+  @SuppressWarnings("unused")
+  private Measurement() {} /* for GWT */
+}
diff --git a/caliper/src/main/java/com/google/caliper/MeasurementSet.java b/caliper/src/main/java/com/google/caliper/MeasurementSet.java
new file mode 100644
index 0000000..eb0b42a
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/MeasurementSet.java
@@ -0,0 +1,264 @@
+/*
+ * 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.common.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A collection of measurements of the same scenario.
+ */
+@SuppressWarnings("serial")
+@GwtCompatible
+public final class MeasurementSet
+    implements Serializable /* for GWT Serialization */ {
+
+  private /*final*/ List<Measurement> measurements;
+  /**
+   * Mapping of user-defined units to relative sizes.
+   */
+  private /*final*/ Map<String, Integer> unitNames;
+
+  private /*final*/ int systemOutCharCount;
+  private /*final*/ int systemErrCharCount;
+
+  public MeasurementSet(Measurement... measurements) {
+    this(0, 0, getUnitNamesFromMeasurements(measurements), Arrays.asList(measurements));
+  }
+
+  private static Map<String, Integer> getUnitNamesFromMeasurements(Measurement... measurements) {
+    Map<String, Integer> unitNameToAssign = null;
+    for (Measurement measurement : measurements) {
+      if (unitNameToAssign == null) {
+        unitNameToAssign = new HashMap<String, Integer>(measurement.getUnitNames());
+      } else if (!unitNameToAssign.equals(measurement.getUnitNames())) {
+        throw new IllegalArgumentException("incompatible unit names: " + unitNameToAssign + " and "
+            + measurement.getUnitNames());
+      }
+    }
+    return unitNameToAssign;
+  }
+
+  /**
+   * Constructor to use directly from plusMeasurement. Skips some excessive checking and takes a
+   * list directly.
+   */
+  private MeasurementSet(int systemOutCharCount, int systemErrCharCount,
+      Map<String, Integer> unitNames, List<Measurement> measurements) {
+    this.systemOutCharCount = systemOutCharCount;
+    this.systemErrCharCount = systemErrCharCount;
+    this.unitNames = unitNames;
+    this.measurements = measurements;
+  }
+
+  /**
+   * This is the same as getUnitNames(), but is for backwards compatibility on the server
+   * when null pointer exceptions need to be avoided.
+   */
+  public Map<String, Integer> getUnitNames(Map<String, Integer> defaultValue) {
+    if (unitNames == null) {
+      return defaultValue;
+    }
+    return new HashMap<String, Integer>(unitNames);
+  }
+
+  public Map<String, Integer> getUnitNames() {
+    return new HashMap<String, Integer>(unitNames);
+  }
+
+  public List<Measurement> getMeasurements() {
+    return new ArrayList<Measurement>(measurements);
+  }
+
+  public int size() {
+    return measurements.size();
+  }
+
+  public int getSystemOutCharCount() {
+    return systemOutCharCount;
+  }
+
+  public int getSystemErrCharCount() {
+    return systemErrCharCount;
+  }
+
+  public List<Double> getMeasurementsRaw() {
+    List<Double> measurementRaw = new ArrayList<Double>();
+    for (Measurement measurement : measurements) {
+      measurementRaw.add(measurement.getRaw());
+    }
+    return measurementRaw;
+  }
+
+  public List<Double> getMeasurementUnits() {
+    List<Double> measurementUnits = new ArrayList<Double>();
+    for (Measurement measurement : measurements) {
+      measurementUnits.add(measurement.getProcessed());
+    }
+    return measurementUnits;
+  }
+
+  /**
+   * Returns the median measurement, with respect to raw units.
+   */
+  public double medianRaw() {
+    return median(getMeasurementsRaw());
+  }
+
+  /**
+   * Returns the median measurement, with respect to user-defined units.
+   */
+  public double medianUnits() {
+    return median(getMeasurementUnits());
+  }
+
+  private double median(List<Double> doubles) {
+    Collections.sort(doubles);
+    int n = doubles.size();
+    return (n % 2 == 0)
+        ? (doubles.get(n / 2 - 1) + doubles.get(n / 2)) / 2
+        : doubles.get(n / 2);
+  }
+
+  /**
+   * Returns the average measurement with respect to raw units.
+   */
+  public double meanRaw() {
+    return mean(getMeasurementsRaw());
+  }
+
+  /**
+   * Returns the average measurement with respect to user-defined units.
+   */
+  public double meanUnits() {
+    return mean(getMeasurementUnits());
+  }
+
+  private double mean(List<Double> doubles) {
+    double sum = 0;
+    for (double d : doubles) {
+      sum += d;
+    }
+    return sum / doubles.size();
+  }
+
+  public double standardDeviationRaw() {
+    return standardDeviation(getMeasurementsRaw());
+  }
+
+  public double standardDeviationUnits() {
+    return standardDeviation(getMeasurementUnits());
+  }
+
+  /**
+   * Returns the standard deviation of the measurements.
+   */
+  private double standardDeviation(List<Double> doubles) {
+    double mean = mean(doubles);
+    double sumOfSquares = 0;
+    for (double d : doubles) {
+      double delta = (d - mean);
+      sumOfSquares += (delta * delta);
+    }
+    return Math.sqrt(sumOfSquares / (doubles.size() - 1));
+  }
+
+  public double minRaw() {
+    return min(getMeasurementsRaw());
+  }
+
+  public double minUnits() {
+    return min(getMeasurementUnits());
+  }
+
+  /**
+   * Returns the minimum measurement.
+   */
+  private double min(List<Double> doubles) {
+    Collections.sort(doubles);
+    return doubles.get(0);
+  }
+
+  public double maxRaw() {
+    return max(getMeasurementsRaw());
+  }
+
+  public double maxUnits() {
+    return max(getMeasurementUnits());
+  }
+
+  /**
+   * Returns the maximum measurement.
+   */
+  private double max(List<Double> doubles) {
+    Collections.sort(doubles, Collections.reverseOrder());
+    return doubles.get(0);
+  }
+
+  /**
+   * Returns a new measurement set that contains the measurements in this set
+   * plus the given additional measurement.
+   */
+  public MeasurementSet plusMeasurement(Measurement measurement) {
+    // verify that this Measurement is compatible with this MeasurementSet
+    if (unitNames != null && !unitNames.equals(measurement.getUnitNames())) {
+      throw new IllegalArgumentException("new measurement incompatible with units of measurement "
+          + "set. Expected " + unitNames + " but got " + measurement.getUnitNames());
+    }
+
+    List<Measurement> resultMeasurements = new ArrayList<Measurement>(measurements);
+    resultMeasurements.add(measurement);
+    Map<String, Integer> newUnitNames = unitNames == null ? measurement.getUnitNames() : unitNames;
+    return new MeasurementSet(systemOutCharCount, systemErrCharCount,
+        newUnitNames, resultMeasurements);
+  }
+
+  public MeasurementSet plusCharCounts(int systemOutCharCount, int systemErrCharCount) {
+    return new MeasurementSet(this.systemOutCharCount + systemOutCharCount,
+        this.systemErrCharCount + systemErrCharCount, unitNames, measurements);
+  }
+
+  @Override public boolean equals(Object o) {
+    return o instanceof MeasurementSet
+        && ((MeasurementSet) o).measurements.equals(measurements)
+        && ((MeasurementSet) o).unitNames.equals(unitNames)
+        && ((MeasurementSet) o).systemOutCharCount == systemOutCharCount
+        && ((MeasurementSet) o).systemErrCharCount == systemErrCharCount;
+  }
+
+  @Override public int hashCode() {
+    return measurements.hashCode()
+        + unitNames.hashCode() * 37
+        + systemOutCharCount * 1373
+        + systemErrCharCount * 53549;
+  }
+
+  @Override public String toString() {
+    return measurements.toString() + " " + unitNames + " "
+        + systemOutCharCount + "/" + systemErrCharCount;
+  }
+
+  @SuppressWarnings("unused")
+  private MeasurementSet() {} // for GWT Serialization
+}
diff --git a/caliper/src/main/java/com/google/caliper/MeasurementType.java b/caliper/src/main/java/com/google/caliper/MeasurementType.java
new file mode 100644
index 0000000..30de69f
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/MeasurementType.java
@@ -0,0 +1,24 @@
+/*
+ * 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.common.annotations.GwtCompatible;
+
+@GwtCompatible
+public enum MeasurementType {
+  TIME, INSTANCE, MEMORY, DEBUG
+}
diff --git a/caliper/src/main/java/com/google/caliper/Measurer.java b/caliper/src/main/java/com/google/caliper/Measurer.java
new file mode 100644
index 0000000..93002e6
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Measurer.java
@@ -0,0 +1,44 @@
+/*
+ * 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.common.base.Supplier;
+
+import java.io.PrintStream;
+
+abstract class Measurer {
+
+  private PrintStream logStream = System.out;
+
+  /**
+   * Sets the stream used to log caliper events.
+   */
+  void setLogStream(PrintStream logStream) {
+    this.logStream = logStream;
+  }
+
+  public abstract MeasurementSet run(Supplier<ConfiguredBenchmark> testSupplier) throws Exception;
+
+  protected void prepareForTest() {
+    System.gc();
+    System.gc();
+  }
+
+  protected final void log(String message) {
+    logStream.println(LogConstants.CALIPER_LOG_PREFIX + message);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/MemoryAllocationMeasurer.java b/caliper/src/main/java/com/google/caliper/MemoryAllocationMeasurer.java
new file mode 100644
index 0000000..a28aabf
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/MemoryAllocationMeasurer.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+public final class MemoryAllocationMeasurer extends AllocationMeasurer {
+
+  public MemoryAllocationMeasurer() {
+    type = "byte";
+  }
+
+  @Override protected long incrementAllocationCount(long oldAllocationCount, int arrayCount,
+      long size) {
+    return oldAllocationCount + size;
+  }
+
+  @Override protected Measurement getMeasurement(ConfiguredBenchmark benchmark, long allocations) {
+    return new Measurement(benchmark.memoryUnitNames(), allocations,
+        benchmark.bytesToUnits(allocations));
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Param.java b/caliper/src/main/java/com/google/caliper/Param.java
new file mode 100644
index 0000000..f4cdf45
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Param.java
@@ -0,0 +1,61 @@
+/*
+ * 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.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * To make your benchmark depend on a parameterized value, create a field with the name you want
+ * this parameter to be known by, and add this annotation. Caliper will inject a value for this
+ * field to each instance it creates. These values come from
+ *
+ * <ul>
+ * <li>The command line, if specified using {@code -Dname=value1,value2,value3}
+ * <li>Otherwise, the {@link #value()} list given in the annotation
+ * <li>Otherwise, Caliper looks for a static method named {@code paramName + "Values"} (for
+ *     example, if the parameter field is {@code size}, it looks for {@code sizeValues()}). The
+ *     method can return any subtype of {@link Iterable}. The contents of that iterable are used as
+ *     the parameter values.
+ * <li>Otherwise, Caliper repeats the previous check looking for a static <em>field</em> instead
+ *     of a method.
+ * <li>Otherwise, if the parameter type is either {@code boolean} or an {@code enum} type, Caliper
+ *     assumes you want all possible values.
+ * <li>Finally, if none of the above match, Caliper will display an error and exit.
+ * </ul>
+ *
+ * <p>Caliper parameters are always strings, but can be converted to other types at the point of
+ * injection. If the type of the field this annotation is applied to is not {@link String}, then the
+ * type class must contain a static {@code fromString(String)}, {@code decode(String)} or {@code
+ * valueOf(String)} method that returns that type, or a constructor accepting only a {@code String}.
+ *
+ * <p>Caliper will test every possible combination of parameter values for your benchmark. For
+ * example, if you have two parameters, {@code -Dletter=a,b,c -Dnumber=1,2}, Caliper will construct
+ * six independent "scenarios" and perform measurement for each one. 
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+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/caliper/src/main/java/com/google/caliper/Parameter.java b/caliper/src/main/java/com/google/caliper/Parameter.java
new file mode 100644
index 0000000..c79bc86
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Parameter.java
@@ -0,0 +1,202 @@
+/*
+ * 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.annotations.VisibleForTesting;
+
+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}.
+ *
+ * @param <T> the (possibly wrapped) type of the parameter field, such as {@link
+ *     String} or {@link Integer}
+ */
+abstract class Parameter<T> {
+
+  private final Field field;
+
+  private Parameter(Field field) {
+    this.field = field;
+  }
+
+  /**
+   * Returns all parameters for the given class.
+   */
+  public static Map<String, Parameter<?>> forClass(Class<? extends Benchmark> suiteClass) {
+    Map<String, Parameter<?>> parameters = new TreeMap<String, Parameter<?>>();
+    for (Field field : suiteClass.getDeclaredFields()) {
+      if (field.isAnnotationPresent(Param.class)) {
+        field.setAccessible(true);
+        Parameter<?> parameter = forField(suiteClass, field);
+        parameters.put(parameter.getName(), parameter);
+      }
+    }
+    return parameters;
+  }
+
+  @VisibleForTesting
+  static Parameter<?> forField(
+      Class<? extends Benchmark> suiteClass, final Field field) {
+    // 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 Iterable<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");
+      if (!Modifier.isStatic(valuesMethod.getModifiers())) {
+        throw new ConfigurationException("Values method must be static " + member);
+      }
+      valuesMethod.setAccessible(true);
+      member = valuesMethod;
+      returnType = valuesMethod.getGenericReturnType();
+      result = new Parameter<Object>(field) {
+        @SuppressWarnings("unchecked") // guarded below
+        @Override public Iterable<Object> values() throws Exception {
+          return (Iterable<Object>) valuesMethod.invoke(null);
+        }
+      };
+    } catch (NoSuchMethodException ignored) {
+    }
+
+    // Now check for a fooValues field
+    try {
+      final Field valuesField = suiteClass.getDeclaredField(field.getName() + "Values");
+      if (!Modifier.isStatic(valuesField.getModifiers())) {
+        throw new ConfigurationException("Values field must be static " + member);
+      }
+      valuesField.setAccessible(true);
+      member = valuesField;
+      if (result != null) {
+        throw new ConfigurationException("Two values members defined for " + field);
+      }
+      returnType = valuesField.getGenericType();
+      result = new Parameter<Object>(field) {
+        @SuppressWarnings("unchecked") // guarded below
+        @Override public Iterable<Object> values() throws Exception {
+          return (Iterable<Object>) valuesField.get(null);
+        }
+      };
+    } catch (NoSuchFieldException ignored) {
+    }
+
+    // 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 Iterable<Object> values() throws Exception {
+          Set<Enum> set = EnumSet.allOf((Class<Enum>) field.getType());
+          return Collections.<Object>unmodifiableSet(set);
+        }
+      };
+    }
+
+    // If it's boolean, default to (true, false)
+    if (member == null && field.getType() == boolean.class) {
+      returnType = Collection.class;
+      result = new Parameter<Object>(field) {
+        @Override public Iterable<Object> values() throws Exception {
+          return Arrays.<Object>asList(Boolean.TRUE, Boolean.FALSE);
+        }
+      };
+    }
+
+    if (result == null) {
+      return new Parameter<Object>(field) {
+        @Override public Iterable<Object> values() {
+          // TODO: need tests to make sure this fails properly when no cmdline params given and
+          // works properly when they are given. Also, can we restructure the code so that we
+          // just throw here instead of later?
+          return Collections.emptySet();
+        }
+      };
+    } else if (!isValidReturnType(returnType)) {
+      throw new ConfigurationException("Invalid return type " + returnType
+          + " for values member " + member + "; must be Collection");
+    }
+    return result;
+  }
+
+  private static boolean isValidReturnType(Type type) {
+    if (type instanceof Class) {
+      return isIterableClass(type);
+    }
+    if (type instanceof ParameterizedType) {
+      return isIterableClass(((ParameterizedType) type).getRawType());
+    }
+    return false;
+  }
+
+  private static boolean isIterableClass(Type returnClass) {
+    return Iterable.class.isAssignableFrom((Class<?>) returnClass);
+  }
+
+  /**
+   * Sets the value of this property to the specified value for the given suite.
+   */
+  public void set(Benchmark suite, Object value) throws Exception {
+    field.set(suite, value);
+  }
+
+  /**
+   * Returns the available values of the property as specified by the suite.
+   */
+  public abstract Iterable<T> values() throws Exception;
+
+  /**
+   * Returns the parameter's type, such as double.class.
+   */
+  public Type getType() {
+    return field.getGenericType();
+  }
+
+  /**
+   * Returns the field's name.
+   */
+  String getName() {
+    return field.getName();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Result.java b/caliper/src/main/java/com/google/caliper/Result.java
new file mode 100644
index 0000000..c57f5a3
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Result.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+/**
+ * Represents an invocation of a benchmark, including the run itself, as well as the environment
+ * in which the run occurred.
+ */
+public final class Result {
+  private /*final*/ Run run;
+  private /*final*/ Environment environment;
+
+  public Result(Run run, Environment environment) {
+    this.run = run;
+    this.environment = environment;
+  }
+
+  public Run getRun() {
+    return run;
+  }
+
+  public Environment getEnvironment() {
+    return environment;
+  }
+
+  @Override public boolean equals(Object o) {
+    return o instanceof Result
+        && ((Result) o).run.equals(run)
+        && ((Result) o).environment.equals(environment);
+  }
+
+  @Override public int hashCode() {
+    return run.hashCode() * 37 + environment.hashCode();
+  }
+
+  @Override public String toString() {
+    return run + "@" + environment;
+  }
+
+  @SuppressWarnings("unused")
+  private Result() {} // for gson
+}
diff --git a/caliper/src/main/java/com/google/caliper/ResultsReader.java b/caliper/src/main/java/com/google/caliper/ResultsReader.java
new file mode 100644
index 0000000..2396dd3
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ResultsReader.java
@@ -0,0 +1,64 @@
+/*
+ * 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.gson.JsonParseException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * Helps with deserialization of results, given uncertainty about the format (xml or json) they
+ * are in.
+ */
+public final class ResultsReader {
+  public Result getResult(InputStream in) throws IOException {
+    // save input into a byte array since we may need to read it twice
+    byte[] postedData = readAllBytes(in);
+    Result result;
+    InputStreamReader baisJsonReader = new InputStreamReader(new ByteArrayInputStream(postedData));
+    try {
+      result = Json.getGsonInstance().fromJson(baisJsonReader, Result.class);
+    } catch (JsonParseException e) {
+      // probably an old client is trying to send data, so try to parse it as XML instead.
+      ByteArrayInputStream baisXml = new ByteArrayInputStream(postedData);
+      try {
+        result = Xml.resultFromXml(baisXml);
+      } catch (Exception e2) {
+        throw new RuntimeException(e);
+      } finally {
+        baisXml.close();
+      }
+    } finally {
+      baisJsonReader.close();
+    }
+    return result;
+  }
+
+  private byte[] readAllBytes(InputStream in) throws IOException {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    byte[] buf = new byte[4096];
+    int read;
+    while ((read = in.read(buf)) != -1) {
+      baos.write(buf, 0, read);
+    }
+    return baos.toByteArray();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Run.java b/caliper/src/main/java/com/google/caliper/Run.java
new file mode 100644
index 0000000..00f9350
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Run.java
@@ -0,0 +1,93 @@
+/**
+ * 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.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * The complete result of a benchmark suite run.
+ *
+ * WARNING: a JSON representation of this class is stored on the app engine server. If any changes
+ * are made to this class, a deserialization adapter must be written for this class to ensure
+ * backwards compatibility.
+ *
+ * <p>Gwt-safe.
+ */
+@SuppressWarnings("serial")
+@GwtCompatible
+public final class Run
+    implements Serializable /* for GWT Serialization */ {
+
+  private /*final*/ Map<Scenario, ScenarioResult> measurements;
+  private /*final*/ String benchmarkName;
+  private /*final*/ long executedTimestamp;
+
+  // TODO: add more run properties such as checksums of the executed code
+
+  public Run(Map<Scenario, ScenarioResult> measurements,
+      String benchmarkName, Date executedTimestamp) {
+    if (benchmarkName == null || executedTimestamp == null) {
+      throw new NullPointerException();
+    }
+
+    this.measurements = new LinkedHashMap<Scenario, ScenarioResult>(measurements);
+    this.benchmarkName = benchmarkName;
+    this.executedTimestamp = executedTimestamp.getTime();
+  }
+
+  public Map<Scenario, ScenarioResult> getMeasurements() {
+    return measurements;
+  }
+
+  public String getBenchmarkName() {
+    return benchmarkName;
+  }
+
+  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)
+          && executedTimestamp == that.executedTimestamp;
+    }
+
+    return false;
+  }
+
+  @Override public int hashCode() {
+    int result = measurements.hashCode();
+    result = result * 37 + benchmarkName.hashCode();
+    result = result * 37 + (int) ((executedTimestamp >> 32) ^ executedTimestamp);
+    return result;
+  }
+
+  @Override public String toString() {
+    return measurements.toString();
+  }
+
+  @SuppressWarnings("unused")
+  private Run() {} // for GWT Serialization
+}
diff --git a/caliper/src/main/java/com/google/caliper/Runner.java b/caliper/src/main/java/com/google/caliper/Runner.java
new file mode 100644
index 0000000..57715c9
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Runner.java
@@ -0,0 +1,438 @@
+/*
+ * 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()));
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Scenario.java b/caliper/src/main/java/com/google/caliper/Scenario.java
new file mode 100644
index 0000000..bc026d8
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Scenario.java
@@ -0,0 +1,80 @@
+/*
+ * 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.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A configured benchmark.
+ *
+ * WARNING: a JSON representation of this class is stored on the app engine server. If any changes
+ * are made to this class, a deserialization adapter must be written for this class to ensure
+ * backwards compatibility.
+ *
+ * <p>Gwt-safe.
+ */
+@SuppressWarnings("serial")
+@GwtCompatible
+public final class Scenario
+    implements Serializable /* for GWT */  {
+
+  static final String VM_KEY = "vm";
+  static final String TRIAL_KEY = "trial";
+
+  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 named set of variables.
+   */
+  public Map<String, String> getVariables(Set<String> names) {
+    Map<String, String> result = new LinkedHashMap<String, String>(variables);
+    result.keySet().retainAll(names);
+    if (!result.keySet().equals(names)) {
+      throw new IllegalArgumentException("Not all of " + names + " are in " + result.keySet());
+    }
+    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;
+  }
+
+  @SuppressWarnings("unused")
+  private Scenario() {} // for GWT
+}
diff --git a/caliper/src/main/java/com/google/caliper/ScenarioResult.java b/caliper/src/main/java/com/google/caliper/ScenarioResult.java
new file mode 100644
index 0000000..1fa687d
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ScenarioResult.java
@@ -0,0 +1,88 @@
+/*
+ * 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.common.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Holds the results for a particular scenario, including timing measurements, memory use
+ * measurements, and event logs for both, recording significant events during measurement.
+ *
+ * WARNING: a JSON representation of this class is stored on the app engine server. If any changes
+ * are made to this class, a deserialization adapter must be written for this class to ensure
+ * backwards compatibility.
+ *
+ * <p>Gwt-safe.
+ */
+@SuppressWarnings({"serial", "FieldMayBeFinal"})
+@GwtCompatible
+public final class ScenarioResult
+    implements Serializable /* for GWT Serialization */ {
+
+  // want these to be EnumMaps, but that upsets GWT
+  private /*final*/ Map<String, MeasurementSet> measurementSetMap
+      = new HashMap<String, MeasurementSet>();
+  private /*final*/ Map<String, String> eventLogMap
+      = new HashMap<String, String>();
+
+  public ScenarioResult(MeasurementSet timeMeasurementSet,
+      String timeEventLog, MeasurementSet instanceMeasurementSet,
+      String instanceEventLog, MeasurementSet memoryMeasurementSet,
+      String memoryEventLog) {
+    if (timeMeasurementSet != null) {
+      measurementSetMap.put(MeasurementType.TIME.toString(), timeMeasurementSet);
+      eventLogMap.put(MeasurementType.TIME.toString(), timeEventLog);
+    }
+    if (instanceMeasurementSet != null) {
+      measurementSetMap.put(MeasurementType.INSTANCE.toString(), instanceMeasurementSet);
+      eventLogMap.put(MeasurementType.INSTANCE.toString(), instanceEventLog);
+    }
+    if (memoryMeasurementSet != null) {
+      measurementSetMap.put(MeasurementType.MEMORY.toString(), memoryMeasurementSet);
+      eventLogMap.put(MeasurementType.MEMORY.toString(), memoryEventLog);
+    }
+  }
+
+  public MeasurementSet getMeasurementSet(MeasurementType type) {
+    return measurementSetMap.get(type.toString());
+  }
+
+  public String getEventLog(MeasurementType type) {
+    return eventLogMap.get(type.toString());
+  }
+
+  @Override public boolean equals(Object o) {
+    return o instanceof ScenarioResult
+        && ((ScenarioResult) o).measurementSetMap.equals(measurementSetMap)
+        && ((ScenarioResult) o).eventLogMap.equals(eventLogMap);
+  }
+
+  @Override public int hashCode() {
+    return measurementSetMap.hashCode() * 37 + eventLogMap.hashCode();
+  }
+
+  @Override public String toString() {
+    return "measurementSetMap: " + measurementSetMap + ", eventLogMap: " + eventLogMap;
+  }
+
+  @SuppressWarnings("unused")
+  private ScenarioResult() {} // for GWT Serialization
+}
diff --git a/caliper/src/main/java/com/google/caliper/ScenarioSelection.java b/caliper/src/main/java/com/google/caliper/ScenarioSelection.java
new file mode 100644
index 0000000..fcfead0
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ScenarioSelection.java
@@ -0,0 +1,253 @@
+/*
+ * 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.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 Set<String> userVms;
+  private final Multimap<String, String> vmParameters;
+  private final String suiteClassName;
+
+  /**
+   * The user parameters specified on the command line. This may be a subset of
+   * the effective user parameters because parameters not specified here may get
+   * default values from the benchmark class.
+   */
+  private final Multimap<String, String> userParameterArguments;
+
+  /**
+   * The actual user parameters we'll use to run in the benchmark. This contains
+   * the userParameterArguments plus the default user parameters.
+   */
+  private Multimap<String, String> userParameters;
+
+  private final int trials;
+  private Benchmark suite;
+
+
+  public ScenarioSelection(Arguments arguments) {
+    this(arguments.getUserVms(), arguments.getVmParameters(), arguments.getSuiteClassName(),
+        arguments.getUserParameters(), arguments.getTrials());
+  }
+
+  public ScenarioSelection(Set<String> userVms, Multimap<String, String> vmParameters,
+      String suiteClassName, Multimap<String, String> userParameterArguments, int trials) {
+    this.userVms = userVms;
+    this.vmParameters = vmParameters;
+    this.suiteClassName = suiteClassName;
+    this.userParameterArguments = userParameterArguments;
+    this.trials = trials;
+  }
+
+  /**
+   * Returns the selected scenarios for this benchmark.
+   */
+  public List<Scenario> select() {
+    prepareSuite();
+    userParameters = computeUserParameters();
+    return createScenarios();
+  }
+
+  /**
+   * Returns a normalized version of {@code scenario}, with information from {@code suite}
+   * assisting in correcting problems.
+   */
+  public Scenario normalizeScenario(Scenario scenario) {
+    // This only applies to SimpleBenchmarks since they accept the special "benchmark"
+    // parameter. This is a special case because SimpleBenchmark is the most commonly
+    // used benchmark class. Have to do this horrible stuff since Benchmark API
+    // doesn't provide scenario-normalization (and it shouldn't), which SimpleBenchmark
+    // requires.
+    if (suite instanceof SimpleBenchmark) {
+      return ((SimpleBenchmark) suite).normalizeScenario(scenario);
+    }
+
+    return scenario;
+  }
+
+  public Set<String> getUserParameterNames() {
+    if (userParameters == null) {
+      throw new IllegalStateException();
+    }
+    return userParameters.keySet();
+  }
+
+  public Set<String> getVmParameterNames() {
+    return vmParameters.keySet();
+  }
+
+  public ConfiguredBenchmark createBenchmark(Scenario scenario) {
+    return suite.createBenchmark(scenario.getVariables(getUserParameterNames()));
+  }
+
+  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 Multimap<String, String> computeUserParameters() {
+    Multimap<String, String> result = LinkedHashMultimap.create();
+    for (String key : suite.parameterNames()) {
+      // first check if the user has specified values
+      Collection<String> userValues = userParameterArguments.get(key);
+      if (!userValues.isEmpty()) {
+        result.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. "
+              + "Did you forget a -D" + key + "=<value> command line argument?");
+        }
+        result.putAll(key, values);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns a complete set of scenarios with every combination of variables.
+   */
+  private List<Scenario> createScenarios() {
+    List<ScenarioBuilder> builders = new ArrayList<ScenarioBuilder>();
+    builders.add(new ScenarioBuilder());
+
+    Map<String, Collection<String>> variables = new LinkedHashMap<String, Collection<String>>();
+    variables.put(Scenario.VM_KEY, userVms.isEmpty() ? VmFactory.defaultVms() : userVms);
+    variables.put(Scenario.TRIAL_KEY, newListOfSize(trials));
+    variables.putAll(userParameters.asMap());
+    variables.putAll(vmParameters.asMap());
+
+    for (Entry<String, Collection<String>> entry : variables.entrySet()) {
+      Iterator<String> values = entry.getValue().iterator();
+      if (!values.hasNext()) {
+        throw new ConfigurationException("Not enough values for " + entry);
+      }
+
+      String firstValue = values.next();
+      for (ScenarioBuilder builder : builders) {
+        builder.variables.put(entry.getKey(), 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.variables.put(entry.getKey(), alternate);
+          builders.add(copy);
+        }
+      }
+    }
+
+    List<Scenario> result = new ArrayList<Scenario>();
+    for (ScenarioBuilder builder : builders) {
+      result.add(normalizeScenario(builder.build()));
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns a list containing {@code count} distinct elements.
+   */
+  private Collection<String> newListOfSize(int count) {
+    List<String> result = new ArrayList<String>();
+    for (int i = 0; i < count; i++) {
+      result.add(Integer.toString(i));
+    }
+    return result;
+  }
+
+  private static class ScenarioBuilder {
+    final Map<String, String> variables = new LinkedHashMap<String, String>();
+
+    ScenarioBuilder copy() {
+      ScenarioBuilder result = new ScenarioBuilder();
+      result.variables.putAll(variables);
+      return result;
+    }
+
+    public Scenario build() {
+      return new Scenario(variables);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/SimpleBenchmark.java b/caliper/src/main/java/com/google/caliper/SimpleBenchmark.java
new file mode 100644
index 0000000..01fc3a8
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/SimpleBenchmark.java
@@ -0,0 +1,227 @@
+/*
+ * 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.ExceptionFromUserCodeException;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A convenience class for implementing benchmarks in plain code.
+ * Implementing classes must have a no-arguments constructor.
+ *
+ * <h3>Benchmarks</h3>
+ * The benchmarks of a suite are defined by . They may be
+ * static. They are not permitted to take parameters . . ..
+ *
+ * <h3>Parameters</h3>
+ * See the {@link Param} documentation to learn about parameters.
+ */
+public abstract class SimpleBenchmark
+    implements Benchmark {
+  private static final Class<?>[] ARGUMENT_TYPES = { int.class };
+
+  private final Map<String, Parameter<?>> parameters;
+  private final Map<String, Method> methods;
+
+  protected SimpleBenchmark() {
+    parameters = Parameter.forClass(getClass());
+    methods = createTimedMethods();
+
+    if (methods.isEmpty()) {
+      throw new ConfigurationException(
+          "No benchmarks defined in " + getClass().getName());
+    }
+  }
+
+  protected void setUp() throws Exception {}
+
+  protected void tearDown() throws Exception {}
+
+  @Override public Set<String> parameterNames() {
+    return ImmutableSet.<String>builder()
+        .add("benchmark")
+        .addAll(parameters.keySet())
+        .build();
+  }
+
+  @Override public Set<String> parameterValues(String parameterName) {
+    if ("benchmark".equals(parameterName)) {
+      return methods.keySet();
+    }
+
+    Parameter<?> parameter = parameters.get(parameterName);
+    if (parameter == null) {
+      throw new IllegalArgumentException();
+    }
+    try {
+      Iterable<?> values = parameter.values();
+
+      ImmutableSet.Builder<String> result = ImmutableSet.builder();
+      for (Object value : values) {
+        result.add(String.valueOf(value));
+      }
+      return result.build();
+    } catch (Exception e) {
+      throw new ExceptionFromUserCodeException(e);
+    }
+  }
+
+  @Override public ConfiguredBenchmark createBenchmark(Map<String, String> parameterValues) {
+    if (!parameterNames().equals(parameterValues.keySet())) {
+      throw new IllegalArgumentException("Invalid parameters specified. Expected "
+          + parameterNames() + " but was " + parameterValues.keySet());
+    }
+
+    String methodName = parameterValues.get("benchmark");
+    final Method method = methods.get(methodName);
+    if (method == null) {
+      throw new IllegalArgumentException("Invalid parameters specified. \"time" + methodName + "\" "
+          + "is not a method of this benchmark.");
+    }
+
+    try {
+      @SuppressWarnings({"ClassNewInstance"}) // can throw any Exception, so we catch all Exceptions
+      final SimpleBenchmark copyOfSelf = getClass().newInstance();
+
+      for (Map.Entry<String, String> entry : parameterValues.entrySet()) {
+        String parameterName = entry.getKey();
+        if ("benchmark".equals(parameterName)) {
+          continue;
+        }
+
+        Parameter<?> parameter = parameters.get(parameterName);
+        Object value = TypeConverter.fromString(entry.getValue(), parameter.getType());
+        parameter.set(copyOfSelf, value);
+      }
+      copyOfSelf.setUp();
+
+      return new ConfiguredBenchmark(copyOfSelf) {
+        @Override public Object run(int reps) throws Exception {
+          try {
+            return method.invoke(copyOfSelf, reps);
+          } catch (InvocationTargetException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof Exception) {
+              throw (Exception) cause;
+            } else if (cause instanceof Error) {
+              throw (Error) cause;
+            } else {
+              throw e;
+            }
+          }
+        }
+
+        @Override public void close() throws Exception {
+          copyOfSelf.tearDown();
+        }
+      };
+    } catch (Exception e) {
+      throw new ExceptionFromUserCodeException(e);
+    }
+  }
+
+  public Scenario normalizeScenario(Scenario scenario) {
+    Map<String, String> variables =
+      new LinkedHashMap<String, String>(scenario.getVariables());
+    // Make sure the scenario contains method names without the prefixed "time". If
+    // it has "time" prefixed, then remove it. Also check whether the user has
+    // accidentally put a lower cased letter first, and fix it if necessary.
+    String benchmark = variables.get("benchmark");
+    Map<String, Method> timedMethods = createTimedMethods();
+    if (timedMethods.get(benchmark) == null) {
+      // try to upper case first character
+      char[] benchmarkChars = benchmark.toCharArray();
+      benchmarkChars[0] = Character.toUpperCase(benchmarkChars[0]);
+      String upperCasedBenchmark = String.valueOf(benchmarkChars);
+      if (timedMethods.get(upperCasedBenchmark) != null) {
+        variables.put("benchmark", upperCasedBenchmark);
+      } else if (benchmark.startsWith("time")) {
+        variables.put("benchmark", benchmark.substring(4));
+      }
+    }
+    return new Scenario(variables);
+  }
+
+  /**
+   * Returns a spec for each benchmark defined in the specified class. The
+   * returned specs have no parameter values; those must be added separately.
+   */
+  private Map<String, Method> createTimedMethods() {
+    ImmutableMap.Builder<String, Method> result = ImmutableMap.builder();
+    for (Method method : getClass().getDeclaredMethods()) {
+      int modifiers = method.getModifiers();
+      if (!method.getName().startsWith("time")) {
+        continue;
+      }
+
+      if (!Modifier.isPublic(modifiers)
+          || Modifier.isStatic(modifiers)
+          || Modifier.isAbstract(modifiers)
+          || !Arrays.equals(method.getParameterTypes(), ARGUMENT_TYPES)) {
+        throw new ConfigurationException("Timed methods must be public, "
+            + "non-static, non-abstract and take a single int parameter. "
+            + "But " + method + " violates these requirements.");
+      }
+
+      result.put(method.getName().substring(4), method);
+    }
+
+    return result.build();
+  }
+
+  @Override public Map<String, Integer> getTimeUnitNames() {
+    return ImmutableMap.of("ns", 1,
+        "us", 1000,
+        "ms", 1000000,
+        "s", 1000000000);
+  }
+
+  @Override public double nanosToUnits(double nanos) {
+    return nanos;
+  }
+
+  @Override public Map<String, Integer> getInstanceUnitNames() {
+    return ImmutableMap.of(" instances", 1,
+        "K instances", 1000,
+        "M instances", 1000000,
+        "B instances", 1000000000);
+  }
+
+  @Override public double instancesToUnits(long instances) {
+    return instances;
+  }
+
+  @Override public Map<String, Integer> getMemoryUnitNames() {
+    return ImmutableMap.of("B", 1,
+        "KiB", 1024,
+        "MiB", 1048576,
+        "GiB", 1073741824);
+  }
+
+  @Override public double bytesToUnits(long bytes) {
+    return bytes;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/StandardVm.java b/caliper/src/main/java/com/google/caliper/StandardVm.java
new file mode 100644
index 0000000..3366eb1
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/StandardVm.java
@@ -0,0 +1,46 @@
+/*
+ * 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.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
+
+final class StandardVm extends Vm {
+
+  @Override public List<String> getVmSpecificOptions(MeasurementType type, Arguments arguments) {
+    if (!arguments.getCaptureVmLog()) {
+      return ImmutableList.of();
+    }
+
+    List<String> result = new ArrayList<String>();
+    result.add("-verbose:gc");
+    result.add("-Xbatch");
+    result.add("-XX:+UseSerialGC");
+    if (type == MeasurementType.TIME) {
+      return Lists.newArrayList("-XX:+PrintCompilation");
+    }
+
+    return result;
+  }
+
+  public static String defaultVmName() {
+    return "java";
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/TimeMeasurer.java b/caliper/src/main/java/com/google/caliper/TimeMeasurer.java
new file mode 100644
index 0000000..2925d2d
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/TimeMeasurer.java
@@ -0,0 +1,180 @@
+/**
+ * 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 static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.caliper.UserException.DoesNotScaleLinearlyException;
+import com.google.caliper.UserException.RuntimeOutOfRangeException;
+import com.google.common.base.Supplier;
+
+/**
+ * Measure's the benchmark's per-trial execution time.
+ */
+class TimeMeasurer extends Measurer {
+
+  private final long warmupNanos;
+  private final long runNanos;
+
+  /**
+   * If the standard deviation of our measurements is within this tolerance, we
+   * won't bother to perform additional measurements.
+   */
+  private static final double SHORT_CIRCUIT_TOLERANCE = 0.01;
+
+  private static final int MAX_TRIALS = 10;
+
+  TimeMeasurer(long warmupMillis, long runMillis) {
+    checkArgument(warmupMillis > 50);
+    checkArgument(runMillis > 50);
+
+    this.warmupNanos = warmupMillis * 1000000;
+    this.runNanos = runMillis * 1000000;
+  }
+
+  private double warmUp(Supplier<ConfiguredBenchmark> testSupplier) throws Exception {
+    long elapsedNanos = 0;
+    long netReps = 0;
+    int reps = 1;
+    boolean definitelyScalesLinearly = false;
+
+    /*
+     * 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.
+     */
+    log("[starting warmup]");
+    while (elapsedNanos < warmupNanos) {
+      long nanos = measureReps(testSupplier.get(), reps);
+      elapsedNanos += nanos;
+
+      netReps += reps;
+      reps *= 2;
+
+      // if reps overflowed, that's suspicious! Check that it time scales with reps
+      if (reps <= 0) {
+        if (!definitelyScalesLinearly) {
+          checkScalesLinearly(testSupplier);
+          definitelyScalesLinearly = true;
+        }
+        reps = Integer.MAX_VALUE;
+      }
+    }
+    log("[ending warmup]");
+
+    double nanosPerExecution = (double) elapsedNanos / netReps;
+    double lowerBound = 0.1;
+    double upperBound = 10000000000.0;
+    if (!(lowerBound <= nanosPerExecution && nanosPerExecution <= upperBound)) {
+      throw new RuntimeOutOfRangeException(nanosPerExecution, lowerBound, upperBound);
+    }
+
+    return nanosPerExecution;
+  }
+
+  /**
+   * Doing half as much work shouldn't take much more than half as much time. If
+   * it does we have a broken benchmark!
+   */
+  private void checkScalesLinearly(Supplier<ConfiguredBenchmark> testSupplier) throws Exception {
+    double half = measureReps(testSupplier.get(), Integer.MAX_VALUE / 2);
+    double one = measureReps(testSupplier.get(), Integer.MAX_VALUE);
+    if (half / one > 0.75) {
+      throw new DoesNotScaleLinearlyException();
+    }
+  }
+
+  /**
+   * Measure the nanos per rep for the given test. This code uses an interesting
+   * strategy to measure the runtime to minimize execution time when execution
+   * time is consistent.
+   * <ol>
+   *   <li>1.0x {@code runMillis} trial is run.
+   *   <li>0.5x {@code runMillis} trial is run.
+   *   <li>1.5x {@code runMillis} trial is run.
+   *   <li>At this point, the standard deviation of these trials is computed. If
+   *       it is within the threshold, the result is returned.
+   *   <li>Otherwise trials continue to be executed until either the threshold
+   *       is satisfied or the maximum number of runs have been executed.
+   * </ol>
+   *
+   * @param testSupplier provides instances of the code under test. A new test
+   *      is created for each iteration because some benchmarks' performance
+   *      depends on which memory was allocated. See SetContainsBenchmark for an
+   *      example.
+   */
+  @Override public MeasurementSet run(Supplier<ConfiguredBenchmark> testSupplier)
+      throws Exception {
+    double estimatedNanosPerRep = warmUp(testSupplier);
+
+    log("[measuring nanos per rep with scale 1.00]");
+    Measurement measurement100 = measure(testSupplier, 1.00, estimatedNanosPerRep);
+    log("[measuring nanos per rep with scale 0.50]");
+    Measurement measurement050 = measure(testSupplier, 0.50, measurement100.getRaw());
+    log("[measuring nanos per rep with scale 1.50]");
+    Measurement measurement150 = measure(testSupplier, 1.50, measurement100.getRaw());
+    MeasurementSet measurementSet =
+        new MeasurementSet(measurement100, measurement050, measurement150);
+
+    for (int i = 3; i < MAX_TRIALS; i++) {
+      double threshold = SHORT_CIRCUIT_TOLERANCE * measurementSet.meanRaw();
+      if (measurementSet.standardDeviationRaw() < threshold) {
+        return measurementSet;
+      }
+
+      log("[performing additional measurement with scale 1.00]");
+      Measurement measurement = measure(testSupplier, 1.00, measurement100.getRaw());
+      measurementSet = measurementSet.plusMeasurement(measurement);
+    }
+
+    return measurementSet;
+  }
+
+  /**
+   * Runs the test method for approximately {@code runNanos * durationScale}
+   * nanos and returns a Measurement of the nanos per rep and units per rep.
+   */
+  private Measurement measure(Supplier<ConfiguredBenchmark> testSupplier,
+      double durationScale, double estimatedNanosPerRep) throws Exception {
+    int reps = (int) (durationScale * runNanos / estimatedNanosPerRep);
+    if (reps == 0) {
+      reps = 1;
+    }
+
+    log("[running trial with " + reps + " reps]");
+    ConfiguredBenchmark benchmark = testSupplier.get();
+    long elapsedTime = measureReps(benchmark, reps);
+    double nanosPerRep = elapsedTime / (double) reps;
+    log(String.format("[took %.2f nanoseconds per rep]", nanosPerRep));
+    return new Measurement(benchmark.timeUnitNames(), nanosPerRep,
+        benchmark.nanosToUnits(nanosPerRep));
+  }
+
+  /**
+   * Returns the total nanos to run {@code reps}.
+   */
+  private long measureReps(ConfiguredBenchmark benchmark, int reps) throws Exception {
+    prepareForTest();
+    log(LogConstants.MEASURED_SECTION_STARTING);
+    long startNanos = System.nanoTime();
+    benchmark.run(reps);
+    long endNanos = System.nanoTime();
+    log(LogConstants.MEASURED_SECTION_DONE);
+    benchmark.close();
+    return endNanos - startNanos;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/TypeConverter.java b/caliper/src/main/java/com/google/caliper/TypeConverter.java
new file mode 100644
index 0000000..c3268e0
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/TypeConverter.java
@@ -0,0 +1,65 @@
+/**
+ * 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.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+/**
+ * Convert objects to and from Strings.
+ */
+final class TypeConverter {
+  private TypeConverter() {}
+
+  public static Object fromString(String value, Type type) {
+    if (type == String.class) {
+      return value;
+    }
+
+    Class<?> c = wrap((Class<?>) type);
+    try {
+      Method m = c.getMethod("valueOf", String.class);
+      m.setAccessible(true); // to permit inner enums, etc.
+      return m.invoke(null, value);
+    } catch (Exception e) {
+      throw new UnsupportedOperationException(
+          "Cannot convert " + value + " of type " + type, e);
+    }
+  }
+
+  // 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/caliper/src/main/java/com/google/caliper/UploadResults.java b/caliper/src/main/java/com/google/caliper/UploadResults.java
new file mode 100644
index 0000000..7dae7af
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/UploadResults.java
@@ -0,0 +1,28 @@
+/*
+ * 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 java.io.File;
+
+/**
+ * Usage: UploadResults <file_or_dir>
+ */
+public class UploadResults {
+  public static void main(String[] args) {
+    new Runner().uploadResultsFileOrDir(new File(args[0]));
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/UserException.java b/caliper/src/main/java/com/google/caliper/UserException.java
new file mode 100644
index 0000000..a4535a7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/UserException.java
@@ -0,0 +1,196 @@
+/*
+ * 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;
+import java.util.Set;
+
+/**
+ * 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 UserException(String error) {
+    super(error);
+  }
+
+  public abstract void display();
+
+  // - - - -
+
+  public abstract static class ErrorInUsageException extends UserException {
+    protected ErrorInUsageException(String error) {
+      super(error);
+    }
+
+    @Override public void display() {
+      String message = getMessage();
+      if (message != null) {
+        System.err.println("Error: " + message);
+      }
+      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: " + getMessage());
+      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 IncompatibleArgumentsException extends ErrorInUsageException {
+    public IncompatibleArgumentsException(String arg) {
+      super("Some arguments passed in are incompatible with: " + arg);
+    }
+  }
+
+  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 DuplicateParameterException extends ErrorInUsageException {
+    public DuplicateParameterException(String arg) {
+      super("Duplicate parameter: " + arg);
+    }
+    public DuplicateParameterException(Set<String> arg) {
+      super("Duplicate parameters: " + arg);
+    }
+  }
+
+  public static class InvalidParameterValueException extends ErrorInUsageException {
+    public InvalidParameterValueException(String arg, String value) {
+      super("Invalid value \"" + value + "\" for parameter: " + arg);
+    }
+  }
+
+  public static class InvalidTrialsException extends ErrorInUsageException {
+    public InvalidTrialsException(String arg) {
+      super("Invalid trials: " + 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 RuntimeOutOfRangeException extends ErrorInUsageException {
+    public RuntimeOutOfRangeException(
+        double nanosPerExecution, double lowerBound, double upperBound) {
+      super("Runtime " + nanosPerExecution + "ns/rep out of range "
+          + lowerBound + "-" + upperBound);
+    }
+  }
+
+  public static class DoesNotScaleLinearlyException extends ErrorInUsageException {
+    public DoesNotScaleLinearlyException() {
+      super("Doing 2x as much work didn't take 2x as much time! "
+          + "Is the JIT optimizing away the body of your benchmark?");
+    }
+  }
+
+  public static class NonConstantMemoryUsage extends ErrorInUsageException {
+    public NonConstantMemoryUsage() {
+      super("Not all reps of the inner loop allocate the same number of times! "
+          + "The reps loop should use a constant number of allocations. "
+          + "Are you using the value of reps inside the loop?");
+    }
+  }
+
+  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.");
+    }
+  }
+
+  public static class InvalidDebugRepsException extends ErrorInUsageException {
+    public InvalidDebugRepsException(String arg) {
+      super("Invalid debug reps: " + arg);
+    }
+  }
+
+  // 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(getMessage());
+      getCause().printStackTrace(System.err);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Vm.java b/caliper/src/main/java/com/google/caliper/Vm.java
new file mode 100644
index 0000000..1cc45a9
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Vm.java
@@ -0,0 +1,46 @@
+/*
+ * 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.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.List;
+
+class Vm {
+  public List<String> getVmSpecificOptions(MeasurementType type, Arguments arguments) {
+    return ImmutableList.of();
+  }
+
+  /**
+   * Returns a process builder to run this VM.
+   *
+   * @param vmArgs the path to the VM followed by VM arguments.
+   * @param applicationArgs arguments to the target process
+   */
+  public ProcessBuilder newProcessBuilder(File workingDirectory, String classPath,
+      ImmutableList<String> vmArgs, String className, ImmutableList<String> applicationArgs) {
+    ProcessBuilder result = new ProcessBuilder();
+    result.directory(workingDirectory);
+    result.command().addAll(vmArgs);
+    result.command().add("-cp");
+    result.command().add(classPath);
+    result.command().add(className);
+    result.command().addAll(applicationArgs);
+    return result;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/VmFactory.java b/caliper/src/main/java/com/google/caliper/VmFactory.java
new file mode 100644
index 0000000..408e34c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/VmFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.common.collect.ImmutableSet;
+
+import java.util.Arrays;
+import java.util.List;
+
+public final class VmFactory {
+  public static ImmutableSet<String> defaultVms() {
+    String vmName = DalvikVm.isDalvikVm()
+        ? DalvikVm.vmName()
+        : StandardVm.defaultVmName();
+    return ImmutableSet.of(vmName);
+  }
+
+  public Vm createVm(Scenario scenario) {
+    List<String> vmList = Arrays.asList(scenario.getVariables().get(Scenario.VM_KEY).split("\\s+"));
+    Vm vm = null;
+    if (!vmList.isEmpty()) {
+      if (vmList.get(0).endsWith("app_process")) {
+        vm = new DalvikVm();
+      } else if (vmList.get(0).endsWith("java")) {
+        vm = new StandardVm();
+      }
+    }
+    if (vm == null) {
+      vm = new Vm();
+    }
+    return vm;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Xml.java b/caliper/src/main/java/com/google/caliper/Xml.java
new file mode 100644
index 0000000..2aeebda
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Xml.java
@@ -0,0 +1,160 @@
+/*
+ * 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.common.collect.ImmutableMap;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * This exists for backwards compatibility with old data, which is stored in XML format.
+ * All new data is stored in JSON.
+ */
+public final class Xml {
+  private static final String DATE_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssz";
+  private static final String ENVIRONMENT_ELEMENT_NAME = "environment";
+  private static final String RESULT_ELEMENT_NAME = "result";
+  private static final String RUN_ELEMENT_NAME = "run";
+  private static final String BENCHMARK_ATTRIBUTE = "benchmark";
+  private static final String EXECUTED_TIMESTAMP_ATTRIBUTE = "executedTimestamp";
+  private static final String OLD_SCENARIO_ELEMENT_NAME = "scenario";
+  // for backwards compatibility, use a different name
+  private static final String SCENARIO_ELEMENT_NAME = "newScenario";
+  private static final String MEASUREMENTS_ELEMENT_NAME = "measurements";
+  private static final String TIME_EVENT_LOG_ELEMENT_NAME = "eventLog";
+
+  private static Result readResultElement(Element element) throws Exception {
+    Environment environment = null;
+    Run run = null;
+    for (Node topLevelNode : XmlUtils.childrenOf(element)) {
+      if (topLevelNode.getNodeName().equals(ENVIRONMENT_ELEMENT_NAME)) {
+        Element environmentElement = (Element) topLevelNode;
+        environment = readEnvironmentElement(environmentElement);
+      } else if (topLevelNode.getNodeName().equals(RUN_ELEMENT_NAME)) {
+        run = readRunElement((Element) topLevelNode);
+      } else {
+        throw new RuntimeException("illegal node name: " + topLevelNode.getNodeName());
+      }
+    }
+
+    if (environment == null || run == null) {
+      throw new RuntimeException("missing environment or run elements");
+    }
+
+    return new Result(run, environment);
+  }
+
+  private static Environment readEnvironmentElement(Element element) {
+    return new Environment(XmlUtils.attributesOf(element));
+  }
+
+  private static Run readRunElement(Element element) throws Exception {
+    String benchmarkName = element.getAttribute(BENCHMARK_ATTRIBUTE);
+    String executedDateString = element.getAttribute(EXECUTED_TIMESTAMP_ATTRIBUTE);
+    Date executedDate = new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US)
+        .parse(executedDateString);
+
+    ImmutableMap.Builder<Scenario, ScenarioResult> measurementsBuilder = ImmutableMap.builder();
+    for (Node scenarioNode : XmlUtils.childrenOf(element)) {
+      Element scenarioElement = (Element) scenarioNode;
+      Scenario scenario = new Scenario(XmlUtils.attributesOf(scenarioElement));
+      ScenarioResult scenarioResult;
+
+      // for backwards compatibility with older runs
+      if (scenarioNode.getNodeName().equals(OLD_SCENARIO_ELEMENT_NAME)) {
+        MeasurementSet measurement =
+            Json.measurementSetFromJson(scenarioElement.getTextContent());
+        scenarioResult = new ScenarioResult(measurement, "",
+            null, null, null, null);
+      } else if (scenarioNode.getNodeName().equals(SCENARIO_ELEMENT_NAME)) {
+        MeasurementSet timeMeasurementSet = null;
+        String eventLog = null;
+        for (Node node : XmlUtils.childrenOf(scenarioElement)) {
+          if (node.getNodeName().equals(MEASUREMENTS_ELEMENT_NAME)) {
+            timeMeasurementSet = Json.measurementSetFromJson(node.getTextContent());
+          } else if (node.getNodeName().equals(TIME_EVENT_LOG_ELEMENT_NAME)) {
+            eventLog = node.getTextContent();
+          } else {
+            throw new RuntimeException("illegal node name: " + node.getNodeName());
+          }
+        }
+        if (timeMeasurementSet == null || eventLog == null) {
+          throw new RuntimeException("missing node \"" + MEASUREMENTS_ELEMENT_NAME + "\" or \""
+              + TIME_EVENT_LOG_ELEMENT_NAME + "\"");
+        }
+        // "new Measurement[0]" used instead of empty varargs argument since MeasurementSet has
+        // an empty private constructor.
+        scenarioResult = new ScenarioResult(timeMeasurementSet, eventLog,
+            null, null, null, null);
+      } else {
+        throw new RuntimeException("illegal node name: " + scenarioNode.getNodeName());
+      }
+
+      measurementsBuilder.put(scenario, scenarioResult);
+    }
+
+    return new Run(measurementsBuilder.build(), benchmarkName, executedDate);
+  }
+
+  /**
+   * Creates a result by decoding XML from the specified stream. The XML should
+   * be consistent with the format emitted by the now deleted runToXml(Run, OutputStream).
+   */
+  public static Run runFromXml(InputStream in) {
+    try {
+      Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+      return readRunElement(document.getDocumentElement());
+    } catch (Exception e) {
+      throw new IllegalStateException("Malformed XML document", e);
+    }
+  }
+
+  /**
+   * Creates an environment by decoding XML from the specified stream. The XML should
+   * be consistent with the format emitted by the now deleted
+   * environmentToXml(Environment, OutputStream).
+   */
+  public static Environment environmentFromXml(InputStream in) {
+    try {
+      Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+      Element environmentElement = document.getDocumentElement();
+      return readEnvironmentElement(environmentElement);
+    } catch (Exception e) {
+      throw new IllegalStateException("Malformed XML document", e);
+    }
+  }
+
+  public static Result resultFromXml(InputStream in) {
+    try {
+      Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+      return readResultElement(document.getDocumentElement());
+    } catch (Exception e) {
+      throw new IllegalStateException("Malformed XML document", e);
+    }
+  }
+
+  private Xml() {}
+}
diff --git a/caliper/src/main/java/com/google/caliper/XmlUtils.java b/caliper/src/main/java/com/google/caliper/XmlUtils.java
new file mode 100644
index 0000000..458e6fa
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/XmlUtils.java
@@ -0,0 +1,49 @@
+/*
+ * 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.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public final class XmlUtils {
+  public static ImmutableList<Node> childrenOf(Node node) {
+    NodeList children = node.getChildNodes();
+    ImmutableList.Builder<Node> result = ImmutableList.builder();
+    for (int i = 0, size = children.getLength(); i < size; i++) {
+      result.add(children.item(i));
+    }
+    return result.build();
+  }
+
+  public static ImmutableMap<String, String> attributesOf(Element element) {
+    NamedNodeMap map = element.getAttributes();
+    ImmutableMap.Builder<String, String> result = ImmutableMap.builder();
+    for (int i = 0, size = map.getLength(); i < size; i++) {
+      Attr attr = (Attr) map.item(i);
+      result.put(attr.getName(), attr.getValue());
+    }
+    return result.build();
+  }
+
+  private XmlUtils() {}
+}
diff --git a/caliper/src/main/java/com/google/caliper/util/InterleavedReader.java b/caliper/src/main/java/com/google/caliper/util/InterleavedReader.java
new file mode 100644
index 0000000..40293bf
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/InterleavedReader.java
@@ -0,0 +1,128 @@
+/*
+ * 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.util;
+
+import com.google.gson.JsonParser;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Reads a stream containing inline JSON objects. Each JSON object is prefixed
+ * by a marker string and suffixed by a newline character.
+ */
+public final class InterleavedReader implements Closeable {
+
+  /**
+   * The length of the scratch buffer to search for markers in. Also acts as an
+   * upper bound on the length of returned strings. Not used as an I/O buffer.
+   */
+  private static final int BUFFER_LENGTH = 80;
+
+  private final String marker;
+  private final BufferedReader reader;
+  private final JsonParser jsonParser = new JsonParser();
+
+  public static final String DEFAULT_MARKER = "//ZxJ/";
+
+  public InterleavedReader(Reader reader) {
+    this(DEFAULT_MARKER, reader);
+  }
+
+  public InterleavedReader(String marker, Reader reader) {
+    if (marker.length() > BUFFER_LENGTH) {
+      throw new IllegalArgumentException("marker.length() > BUFFER_LENGTH");
+    }
+    this.marker = marker;
+    this.reader = reader instanceof BufferedReader
+        ? (BufferedReader) reader
+        : new BufferedReader(reader);
+  }
+
+  /**
+   * Returns the next value in the stream: either a String, a JsonElement, or
+   * null to indicate the end of the stream. Callers should use instanceof to
+   * inspect the return type.
+   */
+  public Object read() throws IOException {
+    char[] buffer = new char[BUFFER_LENGTH];
+    reader.mark(BUFFER_LENGTH);
+    int count = 0;
+    int textEnd;
+
+    while (true) {
+      int r = reader.read(buffer, count, buffer.length - count);
+
+      if (r == -1) {
+        // the input is exhausted; return the remaining characters
+        textEnd = count;
+        break;
+      }
+
+      count += r;
+      int possibleMarker = findPossibleMarker(buffer, count);
+
+      if (possibleMarker != 0) {
+        // return the characters that precede the marker
+        textEnd = possibleMarker;
+        break;
+      }
+
+      if (count < marker.length()) {
+        // the buffer contains only the prefix of a marker so we must read more
+        continue;
+      }
+
+      // we've read a marker so return the value that follows
+      reader.reset();
+      String json = reader.readLine().substring(marker.length());
+      return jsonParser.parse(json);
+    }
+
+    if (count == 0) {
+      return null;
+    }
+
+    // return characters
+    reader.reset();
+    count = reader.read(buffer, 0, textEnd);
+    return new String(buffer, 0, count);
+  }
+
+  @Override public void close() throws IOException {
+    reader.close();
+  }
+
+  /**
+   * Returns the index of marker in {@code chars}, stopping at {@code limit}.
+   * Should the chars end with a prefix of marker, the offset of that prefix
+   * is returned.
+   */
+  int findPossibleMarker(char[] chars, int limit) {
+    search:
+    for (int i = 0; true; i++) {
+      for (int m = 0; m < marker.length() && i + m < limit; m++) {
+        if (chars[i + m] != marker.charAt(m)) {
+          continue search;
+        }
+      }
+      return i;
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/util/LinearTranslation.java b/caliper/src/main/java/com/google/caliper/util/LinearTranslation.java
new file mode 100644
index 0000000..2e52b06
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/LinearTranslation.java
@@ -0,0 +1,48 @@
+/*
+ * 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.util;
+
+import com.google.common.annotations.GwtCompatible;
+
+@GwtCompatible
+public class LinearTranslation {
+  //  y = mx + b
+  private final double m;
+  private final double b;
+
+  // TODO(kevinb): why so high? why even check this at all?
+  private static final double EQUALITY_TOLERANCE = 1.0E-6;
+
+  /**
+   * Constructs a linear translation for which {@code translate(in1) == out1}
+   * and {@code translate(in2) == out2}.
+   *
+   * @throws IllegalArgumentException if {@code in1 == in2}
+   */
+  public LinearTranslation(double in1, double out1, double in2, double out2) {
+    if (Math.abs(in1 - in2) < EQUALITY_TOLERANCE) {
+      throw new IllegalArgumentException("in1 and in2 are approximately equal");
+    }
+    double divisor = in1 - in2;
+    this.m = (out1 - out2) / divisor;
+    this.b = (in1 * out2 - in2 * out1) / divisor;
+  }
+
+  public double translate(double in) {
+    return m * in + b;
+  }
+}
diff --git a/caliper/src/main/resources/CaliperCore.gwt.xml b/caliper/src/main/resources/CaliperCore.gwt.xml
new file mode 100644
index 0000000..fcfe2fe
--- /dev/null
+++ b/caliper/src/main/resources/CaliperCore.gwt.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module>
+  <inherits name='com.google.gwt.user.User'/>
+  <source path="com/google/caliper">
+    <!-- Adding a file? Don't forget to update build.xml! -->
+    <include name="**/LinearTranslation.java"/>
+    <include name="**/Environment.java"/>
+    <include name="**/Run.java"/>
+    <include name="**/Measurement.java"/>
+    <include name="**/MeasurementSet.java"/>
+    <include name="**/ScenarioResult.java"/>
+    <include name="**/MeasurementType.java"/>
+    <include name="**/Scenario.java"/>
+  </source>
+</module>
\ No newline at end of file
diff --git a/caliper/src/test/java/com/google/caliper/AllTests.java b/caliper/src/test/java/com/google/caliper/AllTests.java
new file mode 100644
index 0000000..0e3d1cc
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/AllTests.java
@@ -0,0 +1,33 @@
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class AllTests {
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    suite.addTestSuite(CaliperTest.class);
+    suite.addTestSuite(JsonTest.class);
+    suite.addTestSuite(MeasurementSetTest.class);
+    suite.addTestSuite(ParameterTest.class);
+    suite.addTestSuite(WarmupOverflowTest.class);
+
+    return suite;
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/CaliperTest.java b/caliper/src/test/java/com/google/caliper/CaliperTest.java
new file mode 100644
index 0000000..18857ba
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/CaliperTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.common.base.Supplier;
+import java.util.Map;
+import java.util.Set;
+import junit.framework.TestCase;
+
+public final class CaliperTest extends TestCase {
+
+  /**
+   * Test we detect and fail when benchmarks don't scale properly.
+   * @throws Exception
+   */
+  public void testBenchmarkScalesNonLinearly() throws Exception {
+    TimeMeasurer timeMeasurer = new TimeMeasurer(1000, 1000);
+    try {
+      timeMeasurer.run(new NonLinearTimedRunnable());
+      fail();
+    } catch (UserException.DoesNotScaleLinearlyException e) {
+    }
+  }
+
+  private static class NonLinearTimedRunnable extends ConfiguredBenchmark
+      implements Supplier<ConfiguredBenchmark> {
+    private NonLinearTimedRunnable() {
+      super(new NoOpBenchmark());
+    }
+
+    @Override public ConfiguredBenchmark get() {
+      return this;
+    }
+
+    @Override public Object run(int reps) throws Exception {
+      return null; // broken! doesn't loop reps times.
+    }
+
+    @Override public void close() throws Exception {}
+  }
+
+  private static class NoOpBenchmark implements Benchmark {
+    @Override public Set<String> parameterNames() {
+      return null;
+    }
+
+    @Override public Set<String> parameterValues(String parameterName) {
+      return null;
+    }
+
+    @Override public ConfiguredBenchmark createBenchmark(Map<String, String> parameterValues) {
+      return null;
+    }
+
+    @Override public Map<String, Integer> getTimeUnitNames() {
+      return null;
+    }
+
+    @Override public Map<String, Integer> getInstanceUnitNames() {
+      return null;
+    }
+
+    @Override public Map<String, Integer> getMemoryUnitNames() {
+      return null;
+    }
+
+    @Override public double nanosToUnits(double nanos) {
+      return 0;
+    }
+
+    @Override public double instancesToUnits(long instances) {
+      return 0;
+    }
+
+    @Override public double bytesToUnits(long bytes) {
+      return 0;
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/JsonTest.java b/caliper/src/test/java/com/google/caliper/JsonTest.java
new file mode 100644
index 0000000..cee02b0
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/JsonTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011 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.Date;
+import java.util.Locale;
+import java.util.Map;
+import junit.framework.TestCase;
+
+public final class JsonTest extends TestCase {
+
+  public void testJsonSerialization() {
+    Result original = newSampleResult();
+    String json = Json.getGsonInstance().toJson(original, Result.class);
+    Result reserialized = Json.getGsonInstance().fromJson(json, Result.class);
+    assertEquals(original, reserialized);
+  }
+
+  /**
+   * Caliper's JSON files used to include dates specific to the host machine's
+   * locale. http://code.google.com/p/caliper/issues/detail?id=113
+   */
+  public void testJsonSerializationWithFancyLocale() {
+    Result original = newSampleResult();
+
+    // serialize in one locale...
+    Locale defaultLocale = Locale.getDefault();
+    Locale.setDefault(Locale.ITALY);
+    String json;
+    try {
+      json = Json.getGsonInstance().toJson(original, Result.class);
+    } finally {
+      Locale.setDefault(defaultLocale);
+    }
+
+    // deserialize in another
+    Result reserialized = Json.getGsonInstance().fromJson(json, Result.class);
+    assertEquals(original, reserialized);
+  }
+
+  private Result newSampleResult() {
+    Map<String,Integer> units = ImmutableMap.of("ns", 1);
+    MeasurementSet timeMeasurements = new MeasurementSet(new Measurement(units, 2.0, 2.0));
+    Date executedDate = new Date(0);
+    Scenario scenario = new Scenario(ImmutableMap.of("benchmark", "Foo"));
+    ScenarioResult scenarioResult = new ScenarioResult(
+        timeMeasurements, "log", null, null, null, null);
+    Run run = new Run(ImmutableMap.of(scenario, scenarioResult), "foo.FooBenchmark", executedDate);
+    Environment environment = new Environment(ImmutableMap.of("os.name", "Linux"));
+    return new Result(run, environment);
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/MeasurementSetTest.java b/caliper/src/test/java/com/google/caliper/MeasurementSetTest.java
new file mode 100644
index 0000000..f6f7cbb
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/MeasurementSetTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.common.collect.ImmutableMap;
+import com.google.common.collect.Ordering;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import junit.framework.TestCase;
+
+public class MeasurementSetTest extends TestCase {
+
+  Ordering<Measurement> MEASUREMENT_BY_NANOS = new Ordering<Measurement>() {
+    @Override public int compare(Measurement a, Measurement b) {
+      return Double.compare(a.getRaw(), b.getRaw());
+    }
+  };
+  
+  public void testIncompatibleMeasurements() {
+    Measurement[] measurements = new Measurement[2];
+    measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements[1] = new Measurement(ImmutableMap.of("triplens", 1), 3.8, 7.6);
+    try {
+      new MeasurementSet(measurements);
+      fail("illegal argument exception not thrown");
+    } catch (IllegalArgumentException e) {
+      // success
+    }
+  }
+
+  public void testIncompatibleAddedMeasurements() {
+    Measurement[] measurements = new Measurement[1];
+    measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    MeasurementSet measurementSet = new MeasurementSet(measurements);
+    try {
+      measurementSet.plusMeasurement(new Measurement(ImmutableMap.of("triplens", 1), 3.8, 7.6));
+      fail("illegal argument exception not thrown");
+    } catch (IllegalArgumentException e) {
+      // success
+    }
+  }
+
+  public void testSize() {
+    Measurement[] measurements = new Measurement[3];
+    measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    MeasurementSet measurementSet = new MeasurementSet(measurements);
+    assertEquals(3, measurementSet.size());
+
+    Measurement[] measurements2 = new Measurement[4];
+    measurements2[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements2[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements2[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    measurements2[3] = new Measurement(ImmutableMap.of("doublens", 1), 7.2, 14.4);
+    MeasurementSet measurementSet2 =
+        new MeasurementSet(measurements2);
+    assertEquals(4, measurementSet2.size());
+  }
+
+  public void testPlusMeasurement() {
+    Measurement[] measurements = new Measurement[3];
+    measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    MeasurementSet measurementSet = new MeasurementSet(measurements);
+
+    Measurement[] measurements2 = new Measurement[4];
+    measurements2[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements2[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements2[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    measurements2[3] = new Measurement(ImmutableMap.of("doublens", 1), 7.2, 14.4);
+    MeasurementSet measurementSet2 =
+        new MeasurementSet(measurements2);
+
+    MeasurementSet measurementSet3 = measurementSet.plusMeasurement(measurements2[3]);
+
+    assertDoubleListsEquals(measurementSet2.getMeasurementsRaw(),
+        measurementSet3.getMeasurementsRaw(), 0.0000001);
+    assertDoubleListsEquals(measurementSet2.getMeasurementUnits(),
+        measurementSet3.getMeasurementUnits(), 0.0000001);
+    assertEquals(measurementSet2.getUnitNames(), measurementSet3.getUnitNames());
+
+    List<Measurement> measurementList1 =
+        MEASUREMENT_BY_NANOS.sortedCopy(measurementSet2.getMeasurements());
+    List<Measurement> measurementList2 =
+        MEASUREMENT_BY_NANOS.sortedCopy(measurementSet3.getMeasurements());
+    assertEquals(measurementList1.size(), measurementList2.size());
+    for (int i = 0; i < measurementList1.size(); i++) {
+      assertEquals(measurementList1.get(i).getRaw(),
+          measurementList2.get(i).getRaw());
+      assertEquals(measurementList1.get(i).getProcessed(),
+          measurementList2.get(i).getProcessed());
+    }
+  }
+
+  public void testMedian() {
+    Measurement[] measurements = new Measurement[3];
+    measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    MeasurementSet measurementSet = new MeasurementSet(measurements);
+    assertEquals(2.3, measurementSet.medianRaw(), 0.00000001);
+    assertEquals(4.6, measurementSet.medianUnits(), 0.00000001);
+
+    Measurement[] measurements2 = new Measurement[4];
+    measurements2[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements2[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements2[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    measurements2[3] = new Measurement(ImmutableMap.of("doublens", 1), 7.2, 14.4);
+    MeasurementSet measurementSet2 =
+        new MeasurementSet(measurements2);
+    assertEquals((2.3 + 3.8) / 2, measurementSet2.medianRaw(), 0.00000001);
+    assertEquals((4.6 + 7.6) / 2, measurementSet2.medianUnits(), 0.00000001);
+  }
+
+  public void testMean() {
+    Measurement[] measurements = new Measurement[3];
+    measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    MeasurementSet measurementSet = new MeasurementSet(measurements);
+    assertEquals((1.1 + 3.8 + 2.3) / 3, measurementSet.meanRaw(), 0.00000001);
+    assertEquals((2.2 + 7.6 + 4.6) / 3, measurementSet.meanUnits(), 0.00000001);
+
+    Measurement[] measurements2 = new Measurement[4];
+    measurements2[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements2[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements2[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    measurements2[3] = new Measurement(ImmutableMap.of("doublens", 1), 7.2, 14.4);
+    MeasurementSet measurementSet2 =
+        new MeasurementSet(measurements2);
+    assertEquals((1.1 + 2.3 + 3.8 + 7.2) / 4, measurementSet2.meanRaw(), 0.00000001);
+    assertEquals((2.2 + 4.6 + 7.6 + 14.4) / 4, measurementSet2.meanUnits(), 0.00000001);
+  }
+
+  public void testStandardDeviation() {
+    Measurement[] measurements = new Measurement[3];
+    measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    MeasurementSet measurementSet = new MeasurementSet(measurements);
+    assertEquals(1.35277, measurementSet.standardDeviationRaw(), 0.00001);
+    assertEquals(2.70555, measurementSet.standardDeviationUnits(), 0.00001);
+  }
+
+  public void testMax() {
+    Measurement[] measurements = new Measurement[3];
+    measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    MeasurementSet measurementSet = new MeasurementSet(measurements);
+    assertEquals(3.8, measurementSet.maxRaw(), 0.00000001);
+    assertEquals(7.6, measurementSet.maxUnits(), 0.00000001);
+  }
+
+  public void testMin() {
+    Measurement[] measurements = new Measurement[3];
+    measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    MeasurementSet measurementSet = new MeasurementSet(measurements);
+    assertEquals(1.1, measurementSet.minRaw(), 0.00000001);
+    assertEquals(2.2, measurementSet.minUnits(), 0.00000001);
+  }
+
+  public void testJsonRoundtrip() {
+    Measurement[] measurements = new Measurement[3];
+    measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+    measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+    measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+    MeasurementSet measurementSet = new MeasurementSet(measurements);
+    MeasurementSet roundTripped =
+        Json.measurementSetFromJson(Json.measurementSetToJson(measurementSet));
+    assertDoubleListsEquals(measurementSet.getMeasurementsRaw(),
+        roundTripped.getMeasurementsRaw(), 0.00000001);
+    assertDoubleListsEquals(measurementSet.getMeasurementUnits(),
+        roundTripped.getMeasurementUnits(), 0.00000001);
+    assertEquals(measurementSet.getUnitNames(), roundTripped.getUnitNames());
+  }
+
+  @SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"})
+  public void testFromLegacyString() {
+    MeasurementSet measurementSet = Json.measurementSetFromJson("122.0 133.0 144.0");
+    assertDoubleListsEquals(Arrays.asList(122.0, 133.0, 144.0),
+        measurementSet.getMeasurementsRaw(), 0.00000001);
+    assertDoubleListsEquals(Arrays.asList(122.0, 133.0, 144.0),
+        measurementSet.getMeasurementUnits(), 0.00000001);
+    assertEquals(ImmutableMap.of("ns", 1, "us", 1000, "ms", 1000000, "s", 1000000000),
+        measurementSet.getUnitNames());
+  }
+
+  private void assertDoubleListsEquals(List<Double> expected, List<Double> actual, double epsilon) {
+    assertEquals(expected.size(), actual.size());
+    Collections.sort(expected);
+    Collections.sort(actual);
+    for (int i = 0; i < expected.size(); i++) {
+      assertEquals(expected.get(i), actual.get(i), epsilon);
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/ParameterTest.java b/caliper/src/test/java/com/google/caliper/ParameterTest.java
new file mode 100644
index 0000000..9063261
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/ParameterTest.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.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+public class ParameterTest extends TestCase {
+
+  public static class A extends SimpleBenchmark {
+    @Param({"value1", "value2"}) String param;
+  }
+
+  public void testFromAnnotation() throws Exception {
+    Map<String,Parameter<?>> map = Parameter.forClass(A.class);
+    Parameter<?> p = map.get("param");
+    assertEquals("param", p.getName());
+    assertEquals(String.class, p.getType());
+
+    checkParameterValues(A.class, "value1", "value2");
+  }
+
+  public enum Foo { VALUE1, VALUE2 }
+
+  public static class H extends SimpleBenchmark {
+    @Param Foo param;
+  }
+
+  public void testAllEnums() throws Exception {
+    checkParameterValues(H.class, Foo.VALUE1, Foo.VALUE2);
+  }
+
+  public static class I extends SimpleBenchmark {
+    @Param boolean param;
+  }
+
+  public void testBoolean() throws Exception {
+    checkParameterValues(I.class, true, false);
+  }
+
+  private static void checkParameterValues(Class<? extends SimpleBenchmark> bClass,
+      Object... expected) throws Exception {
+    Map<String,Parameter<?>> map = Parameter.forClass(bClass);
+    assertEquals(1, map.size());
+    Parameter<?> p = map.get("param");
+    List<Object> values = ImmutableList.copyOf(p.values());
+    assertEquals(Arrays.asList(expected), values);
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/WarmupOverflowTest.java b/caliper/src/test/java/com/google/caliper/WarmupOverflowTest.java
new file mode 100644
index 0000000..85e59af
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/WarmupOverflowTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.DoesNotScaleLinearlyException;
+import com.google.common.util.concurrent.SimpleTimeLimiter;
+import com.google.common.util.concurrent.TimeLimiter;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import junit.framework.TestCase;
+
+/**
+ * Test exposing an issue where any warmup that completes enough executions to reach
+ * Integer.MAX_VALUE reps (either because the benchmark code is optimized away or because the
+ * warmupMillis are long enough compared to the benchmark execution time).
+ */
+public class WarmupOverflowTest extends TestCase {
+  private TimeLimiter timeLimiter;
+
+  @Override public void setUp() {
+    timeLimiter = new SimpleTimeLimiter(Executors.newSingleThreadExecutor());
+  }
+
+  public void testOptimizedAwayBenchmarkDoesNotTakeTooLongToRun() throws Exception {
+    try {
+      timeLimiter.callWithTimeout(new Callable<Void>() {
+        @Override public Void call() throws Exception {
+          InProcessRunner runner = new InProcessRunner();
+          runner.run(OptimizedAwayBenchmark.class.getName(), "--warmupMillis", "3000",
+              "--measurementType", "TIME");
+          return null;
+        }
+      }, 90, TimeUnit.SECONDS, false);
+    } catch (DoesNotScaleLinearlyException expected) {
+    }
+  }
+
+  public void testLongWarmupMillisDoesNotTakeTooLongToRun() throws Exception {
+    timeLimiter.callWithTimeout(new Callable<Void>() {
+      @Override public Void call() throws Exception {
+        InProcessRunner runner = new InProcessRunner();
+        runner.run(RelativelyFastBenchmark.class.getName(), "--warmupMillis", "8000",
+            "--runMillis", "51", "--measurementType", "TIME");
+        return null;
+      }
+    }, 90, TimeUnit.SECONDS, false);
+  }
+
+  public static class OptimizedAwayBenchmark extends SimpleBenchmark {
+    public void timeIsNullOrEmpty(int reps) {
+      for (int i = 0; i < reps; i++) {
+        // do nothing!
+      }
+    }
+  }
+
+  public static class RelativelyFastBenchmark extends SimpleBenchmark {
+    public long timeSqrt(int reps) {
+      long result = 0;
+      for(int i = 0; i < reps; i++) {
+        result += Math.sqrt(81);
+      }
+      return result;
+    }
+  }
+}
diff --git a/examples/pom.xml b/examples/pom.xml
new file mode 100644
index 0000000..7352d7b
--- /dev/null
+++ b/examples/pom.xml
@@ -0,0 +1,88 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.google.caliper</groupId>
+  <artifactId>caliper-examples</artifactId>
+  <packaging>jar</packaging>
+  <version>0.5-rc1</version>
+  <inceptionYear>2009</inceptionYear>
+  <name>Caliper Examples</name>
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>5</version>
+  </parent>
+  <url>http://code.google.com/p/caliper/</url>
+  <description>Caliper Examples</description>
+  <licenses>
+    <license>
+      <name>The Apache Software License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+  <scm>
+    <connection>scm:git:http://code.google.com/p/caliper/examples</connection>
+    <developerConnection>scm:git:git:http://code.google.com/p/caliper/examples</developerConnection>
+    <url>http://caliper.codegoogle.com/svn/trunk/examples</url>
+  </scm>
+  <issueManagement>
+    <system>Google Code Issue Tracking</system>
+    <url>http://code.google.com/p/caliper/issues/list</url>
+  </issueManagement>
+  <organization>
+    <name>Google, Inc.</name>
+    <url>http://www.google.com</url>
+  </organization>
+  <dependencies>
+    <dependency>
+      <groupId>com.google.caliper</groupId>
+      <artifactId>caliper</artifactId>
+      <version>0.5-rc1</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.2</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <defaultGoal>package</defaultGoal>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.3.2</version>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-eclipse-plugin</artifactId>
+        <version>2.8</version>
+        <configuration>
+          <downloadSources>true</downloadSources>
+          <downloadJavadocs>true</downloadJavadocs>
+          <workspace>../eclipse-ws/</workspace>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-release-plugin</artifactId>
+        <version>2.1</version>
+        <configuration>
+          <arguments>-DenableCiProfile=true</arguments>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <developers>
+    <developer>
+      <name>Jesse Wilson</name>
+      <organization>Google Inc.</organization>
+    </developer>
+  </developers>
+</project>
diff --git a/examples/src/main/java/examples/ArraySortBenchmark.java b/examples/src/main/java/examples/ArraySortBenchmark.java
new file mode 100644
index 0000000..f42390f
--- /dev/null
+++ b/examples/src/main/java/examples/ArraySortBenchmark.java
@@ -0,0 +1,102 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Measures sorting on different distributions of integers.
+ */
+public class ArraySortBenchmark extends SimpleBenchmark {
+
+  @Param({"10", "100", "1000", "10000"}) private int length;
+
+  @Param private Distribution distribution;
+
+  private int[] values;
+  private int[] copy;
+
+  @Override protected void setUp() throws Exception {
+    values = distribution.create(length);
+    copy = new int[length];
+  }
+
+  public void timeSort(int reps) {
+    for (int i = 0; i < reps; i++) {
+      System.arraycopy(values, 0, copy, 0, values.length);
+      Arrays.sort(copy);
+    }
+  }
+
+  public enum Distribution {
+    SAWTOOTH {
+      @Override
+      int[] create(int length) {
+        int[] result = new int[length];
+        for (int i = 0; i < length; i += 5) {
+          result[i] = 0;
+          result[i + 1] = 1;
+          result[i + 2] = 2;
+          result[i + 3] = 3;
+          result[i + 4] = 4;
+        }
+        return result;
+      }
+    },
+    INCREASING {
+      @Override
+      int[] create(int length) {
+        int[] result = new int[length];
+        for (int i = 0; i < length; i++) {
+          result[i] = i;
+        }
+        return result;
+      }
+    },
+    DECREASING {
+      @Override
+      int[] create(int length) {
+        int[] result = new int[length];
+        for (int i = 0; i < length; i++) {
+          result[i] = length - i;
+        }
+        return result;
+      }
+    },
+    RANDOM {
+      @Override
+      int[] create(int length) {
+        Random random = new Random();
+        int[] result = new int[length];
+        for (int i = 0; i < length; i++) {
+          result[i] = random.nextInt();
+        }
+        return result;
+      }
+    };
+
+    abstract int[] create(int length);
+  }
+
+  public static void main(String[] args) throws Exception {
+    Runner.main(ArraySortBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/BitSetBenchmark.java b/examples/src/main/java/examples/BitSetBenchmark.java
new file mode 100644
index 0000000..3154b01
--- /dev/null
+++ b/examples/src/main/java/examples/BitSetBenchmark.java
@@ -0,0 +1,186 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.BitSet;
+import java.util.Random;
+
+/**
+ * A simple example of a benchmark for BitSet showing some of the issues with
+ * micro-benchmarking.
+ *
+ * <p>The following is a discussion of how the benchmarks evolved and what they
+ * may (or may not) tell us. This discussion is based on the following set of
+ * results:
+ *
+ * <p><pre>
+ *  0% Scenario{vm=java, benchmark=SetBitSetX64} 233.45ns; σ=0.31ns @ 3 trials
+ * 20% Scenario{vm=java, benchmark=SetMaskX64} 116.62ns; σ=0.09ns @ 3 trials
+ * 40% Scenario{vm=java, benchmark=CharsToBitSet} 748.40ns; σ=23.52ns @ 10 trials
+ * 60% Scenario{vm=java, benchmark=CharsToMask} 198.55ns; σ=9.46ns @ 10 trials
+ * 80% Scenario{vm=java, benchmark=BaselineIteration} 67.85ns; σ=0.44ns @ 3 trials
+ *
+ *         benchmark   ns logarithmic runtime
+ *      SetBitSetX64  233 XXXXXXXXX|||||||||||||||
+ *        SetMaskX64  117 XXXX|||||||||||||||||
+ *     CharsToBitSet  748 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ *       CharsToMask  199 XXXXXXX||||||||||||||||
+ * BaselineIteration   68 XX|||||||||||||||||
+ * </pre>
+ *
+ * <p>Initially things look simple. The {@link #timeSetBitSetX64(int)} benchmark
+ * takes approximately twice as long as {@link #timeSetMaskX64(int)}. However
+ * the inner loops in these benchmarks have almost no content, so a more
+ * 'real world' benchmark was devised in an attempt to back up these results.
+ *
+ * <p>The {@link #timeCharsToMask(int)} and {@link #timeCharsToBitSet(int)}
+ * benchmarks convert a simple char[] of '1's and '0's to a corresponding BitSet
+ * or bit mask. These also processes 64 bits per iteration and so appears to be
+ * doing the same amount of work as the first benchmarks.
+ *
+ * <p>Additionally the {@link BitSetBenchmark#timeBaselineIteration(int)}
+ * benchmark attempts to measure the raw cost of looping through and reading the
+ * source data.
+ *
+ * <p>When comparing the benchmarks that use bit masking, we see that the
+ * measured time of the SetMaskX64 benchmark (117ns) is roughly the same
+ * as the CharsToMask benchmark (199ns) with the BaselineIteration time (68ms)
+ * subtracted from it. This gives us some confidence that both benchmarks are
+ * resulting in the same underlying work on the CPU.
+ *
+ * <p>However the CharsToBitSet and the SetBitSetX64 benchmarks differ very
+ * significantly (approximately 3x) even when accounting for the
+ * BaselineIteration result. This suggests that the performance of
+ * {@link BitSet#set} is quite dependent on the surrounding code and how
+ * it is optimized by the JVM.
+ *
+ * <p>The conclusions we can draw from this are:
+ *
+ * <p><b>1:</b> Using BitSet is slower than using bit masks directly. At best it
+ * seems about 2x slower than a bit mask, but could easily be 5x slower in real
+ * applications.
+ *
+ * <p>While these are only estimates, we can conclude that when performance is
+ * important and where bit set operations occur in tight loops, bit masks
+ * should be used in favor of BitSets.
+ *
+ * <p><b>2:</b>Overly simplistic benchmarks can give a very false impression of
+ * performance.
+ */
+public class BitSetBenchmark extends SimpleBenchmark {
+  private BitSet bitSet;
+  private char[] bitString;
+
+  @Override protected void setUp() throws Exception {
+    bitSet = new BitSet(64);
+    bitString = new char[64];
+    Random r = new Random();
+    for (int n = 0; n < 64; n++) {
+      bitString[n] = r.nextBoolean() ? '1' : '0';
+    }
+  }
+
+  /**
+   * This benchmark attempts to measure performance of {@link BitSet#set}.
+   */
+  public int timeSetBitSetX64(int reps) {
+    long count = 64L * reps;
+    for (int i = 0; i < count; i++) {
+      bitSet.set(i & 0x3F, true);
+    }
+    return bitSet.hashCode();
+  }
+
+  /**
+   * This benchmark attempts to measure performance of direct bit-manipulation.
+   */
+  public long timeSetMaskX64(int reps) {
+    long count = 64L * reps;
+    long bitMask = 0L;
+    for (int i = 0; i < count; i++) {
+      bitMask |= 1 << (i & 0x3F);
+    }
+    return bitMask;
+  }
+
+  /**
+   * This benchmark parses a char[] of 1's and 0's into a BitSet. Results from
+   * this benchmark should be comparable with those from
+   * {@link #timeCharsToMask(int)}.
+   */
+  public String timeCharsToBitSet(int reps) {
+    /*
+     * This benchmark now measures the complete parsing of a char[] rather than
+     * a single invocation of {@link BitSet#set}. However this fine because
+     * it is intended to be a comparative benchmark.
+     */
+    for (int i = 0; i < reps; i++) {
+      for (int n = 0; n < bitString.length; n++) {
+        bitSet.set(n, bitString[n] == '1');
+      }
+    }
+    return bitSet.toString();
+  }
+
+  /**
+   * This benchmark parses a char[] of 1's and 0's into a bit mask. Results from
+   * this benchmark should be comparable with those from
+   * {@link #timeCharsToBitSet(int)}.
+   */
+  public long timeCharsToMask(int reps) {
+    /*
+     * Comparing results we see a far more realistic sounding result whereby
+     * using a bit mask is a little over 4x faster than using BitSet.
+     */
+    long bitMask = 0;
+    for (int i = 0; i < reps; i++) {
+      for (int n = 0; n < bitString.length; n++) {
+        long m = 1 << n;
+        if (bitString[n] == '1') {
+          bitMask |= m;
+        } else {
+          bitMask &= ~m;
+        }
+      }
+    }
+    return bitMask;
+  }
+
+  /**
+   * This benchmark attempts to measure the baseline cost of both
+   * {@link #timeCharsToBitSet(int)} and {@link #timeCharsToMask(int)}.
+   * It does this by unconditionally summing the character values of the char[].
+   * This is as close to a no-op case as we can expect to get without unwanted
+   * over-optimization.
+   */
+  public long timeBaselineIteration(int reps) {
+    int badHash = 0;
+    for (int i = 0; i < reps; i++) {
+      for (int n = 0; n < bitString.length; n++) {
+        badHash += bitString[n];
+      }
+    }
+    return badHash;
+  }
+
+  // TODO: remove this from all examples when IDE plugins are ready
+  public static void main(String[] args) throws Exception {
+      Runner.main(BitSetBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/CharacterBenchmark.java b/examples/src/main/java/examples/CharacterBenchmark.java
new file mode 100644
index 0000000..1e013af
--- /dev/null
+++ b/examples/src/main/java/examples/CharacterBenchmark.java
@@ -0,0 +1,300 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Tests various Character methods, intended for testing multiple
+ * implementations against each other.
+ */
+public class CharacterBenchmark extends SimpleBenchmark {
+
+    @Param private CharacterSet characterSet;
+
+    @Param private Overload overload;
+
+    private char[] chars;
+
+    @Override protected void setUp() throws Exception {
+        this.chars = characterSet.chars;
+    }
+
+    public enum Overload { CHAR, INT }
+
+    public enum CharacterSet {
+        ASCII(128),
+        UNICODE(65536);
+        final char[] chars;
+        CharacterSet(int size) {
+            this.chars = new char[65536];
+            for (int i = 0; i < 65536; ++i) {
+                chars[i] = (char) (i % size);
+            }
+        }
+    }
+
+    // A fake benchmark to give us a baseline.
+    public boolean timeIsSpace(int reps) {
+        boolean dummy = false;
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    dummy ^= ((char) ch == ' ');
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    dummy ^= (ch == ' ');
+                }
+            }
+        }
+        return dummy;
+    }
+
+    public void timeDigit(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.digit(chars[ch], 10);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.digit((int) chars[ch], 10);
+                }
+            }
+        }
+    }
+
+    public void timeGetNumericValue(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.getNumericValue(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.getNumericValue((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsDigit(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isDigit(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isDigit((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsIdentifierIgnorable(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isIdentifierIgnorable(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isIdentifierIgnorable((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsJavaIdentifierPart(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isJavaIdentifierPart(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isJavaIdentifierPart((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsJavaIdentifierStart(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isJavaIdentifierStart(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isJavaIdentifierStart((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsLetter(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLetter(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLetter((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsLetterOrDigit(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLetterOrDigit(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLetterOrDigit((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsLowerCase(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLowerCase(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLowerCase((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsSpaceChar(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isSpaceChar(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isSpaceChar((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsUpperCase(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isUpperCase(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isUpperCase((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsWhitespace(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isWhitespace(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isWhitespace((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeToLowerCase(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.toLowerCase(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.toLowerCase((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeToUpperCase(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.toUpperCase(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.toUpperCase((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    // TODO: remove this from all examples when IDE plugins are ready
+    public static void main(String[] args) throws Exception {
+        Runner.main(CharacterBenchmark.class, args);
+    }
+}
diff --git a/examples/src/main/java/examples/CompressionSizeBenchmark.java b/examples/src/main/java/examples/CompressionSizeBenchmark.java
new file mode 100644
index 0000000..90ddd39
--- /dev/null
+++ b/examples/src/main/java/examples/CompressionSizeBenchmark.java
@@ -0,0 +1,90 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.api.Benchmark;
+import com.google.caliper.model.ArbitraryMeasurement;
+import com.google.caliper.runner.CaliperMain;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.Deflater;
+
+/**
+ * Example "arbitrary measurement" benchmark.
+ */
+public class CompressionSizeBenchmark extends Benchmark {
+
+  @Param({
+      "this string will compress badly",
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+      "asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf"})
+  private String toCompress;
+  @Param({"bestCompression", "bestSpeed", "noCompression", "huffmanOnly"})
+  private String compressionLevel;
+
+  private double compressionRatio;
+
+  public static final Map<String, Integer> compressionLevelMap = new HashMap<String, Integer>();
+  static {
+      compressionLevelMap.put("bestCompression", Deflater.BEST_COMPRESSION);
+      compressionLevelMap.put("bestSpeed", Deflater.BEST_SPEED);
+      compressionLevelMap.put("noCompression", Deflater.NO_COMPRESSION);
+      compressionLevelMap.put("huffmanOnly", Deflater.HUFFMAN_ONLY);
+  }
+
+  public long timeSimpleCompression(int reps) {
+    long dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += compress(toCompress.getBytes()).length;
+    }
+    return dummy;
+  }
+
+  @ArbitraryMeasurement(units = ":1", description = "ratio of uncompressed to compressed")
+  public double compressionSize() {
+    byte[] initialBytes = toCompress.getBytes();
+    byte[] finalBytes = compress(initialBytes);
+    compressionRatio = (double) initialBytes.length / (double) finalBytes.length;
+    return compressionRatio;
+  }
+
+  private byte[] compress(byte[] bytes) {
+    Deflater compressor = new Deflater();
+    compressor.setLevel(compressionLevelMap.get(compressionLevel));
+    compressor.setInput(bytes);
+    compressor.finish();
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    byte[] buf = new byte[1024];
+    while (!compressor.finished()) {
+      int count = compressor.deflate(buf);
+      bos.write(buf, 0, count);
+    }
+    try {
+      bos.close();
+    } catch (IOException e) {
+    }
+    return bos.toByteArray();
+  }
+
+  public static void main(String[] args) {
+    CaliperMain.main(CompressionSizeBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/ContainsBenchmark.java b/examples/src/main/java/examples/ContainsBenchmark.java
new file mode 100644
index 0000000..01bb8c6
--- /dev/null
+++ b/examples/src/main/java/examples/ContainsBenchmark.java
@@ -0,0 +1,78 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+public class ContainsBenchmark extends SimpleBenchmark {
+  @Param({"0", "25", "50", "75", "100"}) private int percentNulls;
+  @Param({"100", "1000", "10000"}) private int containsPerRep;
+
+  /** the set under test */
+  private final Set<String> set = new HashSet<String>();
+
+  /** twenty-five percent nulls */
+  private final List<Object> queries = new ArrayList<Object>();
+
+  @Override protected void setUp() {
+    set.addAll(Arrays.asList("str1", "str2", "str3", "str4"));
+    int nullThreshold = percentNulls * containsPerRep / 100;
+    for (int i = 0; i < nullThreshold; i++) {
+      queries.add(null);
+    }
+    for (int i = nullThreshold; i < containsPerRep; i++) {
+      queries.add(new Object());
+    }
+    Collections.shuffle(queries, new Random(0));
+  }
+
+  @Override public Map<String, Integer> getTimeUnitNames() {
+    Map<String, Integer> unitNames = new HashMap<String, Integer>();
+    unitNames.put("ns/contains", 1);
+    unitNames.put("us/contains", 1000);
+    unitNames.put("ms/contains", 1000000);
+    unitNames.put("s/contains", 1000000000);
+    return unitNames;
+  }
+
+  @Override public double nanosToUnits(double nanos) {
+    return nanos / containsPerRep;
+  }
+
+  public void timeContains(int reps) {
+    for (int i = 0; i < reps; i++) {
+      for (Object query : queries) {
+        set.contains(query);
+      }
+    }
+  }
+
+  public static void main(String[] args) {
+    Runner.main(ContainsBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/CopyArrayBenchmark.java b/examples/src/main/java/examples/CopyArrayBenchmark.java
new file mode 100644
index 0000000..fed0950
--- /dev/null
+++ b/examples/src/main/java/examples/CopyArrayBenchmark.java
@@ -0,0 +1,356 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Tests each of four ways to copy an array, for all nine array types.
+ *
+ * <p>Once upon a time, {@code clone} was much slower than the other array copy techniques, but
+ * that was fixed in Sun bug:
+ *
+ * <a href="http://bugs.sun.com/view_bug.do?bug_id=6428387">
+ * array clone() much slower than Arrays.copyOf</a>
+ *
+ * at which time all copy methods were equally efficient.
+ *
+ * <p>Recent (2011) measurements with OpenJDK 7 on Linux are less clear.  Results suggests that:
+ *
+ * <ul>
+ * <li>The different methods of copying have indistinguishable performance with hotspot server for
+ *     all nine types, except that the naive LOOP is slower.
+ *     With the "client" compiler, LOOP beats CLONE, which is the slowest.
+ * <li>As array sizes get large, the runtime is indeed proportional to the size of the array in
+ *     memory (boolean arrays count as byte arrays!).
+ * </ul>
+ */
+public class CopyArrayBenchmark extends SimpleBenchmark {
+  public enum Strategy {
+    CLONE {
+      @Override Object[] copy(Object[] array) {
+        return array.clone();
+      }
+      @Override boolean[] copy(boolean[] array) {
+        return array.clone();
+      }
+      @Override byte[] copy(byte[] array) {
+        return array.clone();
+      }
+      @Override char[] copy(char[] array) {
+        return array.clone();
+      }
+      @Override double[] copy(double[] array) {
+        return array.clone();
+      }
+      @Override float[] copy(float[] array) {
+        return array.clone();
+      }
+      @Override int[] copy(int[] array) {
+        return array.clone();
+      }
+      @Override long[] copy(long[] array) {
+        return array.clone();
+      }
+      @Override short[] copy(short[] array) {
+        return array.clone();
+      }
+    },
+    ARRAYS_COPYOF {
+      @Override Object[] copy(Object[] array) {
+        return Arrays.copyOf(array, array.length);
+      }
+      @Override boolean[] copy(boolean[] array) {
+        return Arrays.copyOf(array, array.length);
+      }
+      @Override byte[] copy(byte[] array) {
+        return Arrays.copyOf(array, array.length);
+      }
+      @Override char[] copy(char[] array) {
+        return Arrays.copyOf(array, array.length);
+      }
+      @Override double[] copy(double[] array) {
+        return Arrays.copyOf(array, array.length);
+      }
+      @Override float[] copy(float[] array) {
+        return Arrays.copyOf(array, array.length);
+      }
+      @Override int[] copy(int[] array) {
+        return Arrays.copyOf(array, array.length);
+      }
+      @Override long[] copy(long[] array) {
+        return Arrays.copyOf(array, array.length);
+      }
+      @Override short[] copy(short[] array) {
+        return Arrays.copyOf(array, array.length);
+      }
+    },
+    SYSTEM_ARRAYCOPY {
+      @Override Object[] copy(Object[] array) {
+        Object[] copy = new Object[array.length];
+        System.arraycopy(array, 0, copy, 0, array.length);
+        return copy;
+      }
+      @Override boolean[] copy(boolean[] array) {
+        boolean[] copy = new boolean[array.length];
+        System.arraycopy(array, 0, copy, 0, array.length);
+        return copy;
+      }
+      @Override byte[] copy(byte[] array) {
+        byte[] copy = new byte[array.length];
+        System.arraycopy(array, 0, copy, 0, array.length);
+        return copy;
+      }
+      @Override char[] copy(char[] array) {
+        char[] copy = new char[array.length];
+        System.arraycopy(array, 0, copy, 0, array.length);
+        return copy;
+      }
+      @Override double[] copy(double[] array) {
+        double[] copy = new double[array.length];
+        System.arraycopy(array, 0, copy, 0, array.length);
+        return copy;
+      }
+      @Override float[] copy(float[] array) {
+        float[] copy = new float[array.length];
+        System.arraycopy(array, 0, copy, 0, array.length);
+        return copy;
+      }
+      @Override int[] copy(int[] array) {
+        int[] copy = new int[array.length];
+        System.arraycopy(array, 0, copy, 0, array.length);
+        return copy;
+      }
+      @Override long[] copy(long[] array) {
+        long[] copy = new long[array.length];
+        System.arraycopy(array, 0, copy, 0, array.length);
+        return copy;
+      }
+      @Override short[] copy(short[] array) {
+        short[] copy = new short[array.length];
+        System.arraycopy(array, 0, copy, 0, array.length);
+        return copy;
+      }
+    },
+    LOOP {
+      @Override Object[] copy(Object[] array) {
+        int len = array.length;
+        Object[] copy = new Object[len];
+        for (int i = 0; i < len; i++) {
+          copy[i] = array[i];
+        }
+        return copy;
+      }
+      @Override boolean[] copy(boolean[] array) {
+        int len = array.length;
+        boolean[] copy = new boolean[array.length];
+        for (int i = 0; i < len; i++) {
+          copy[i] = array[i];
+        }
+        return copy;
+      }
+      @Override byte[] copy(byte[] array) {
+        int len = array.length;
+        byte[] copy = new byte[array.length];
+        for (int i = 0; i < len; i++) {
+          copy[i] = array[i];
+        }
+        return copy;
+      }
+      @Override char[] copy(char[] array) {
+        int len = array.length;
+        char[] copy = new char[array.length];
+        for (int i = 0; i < len; i++) {
+          copy[i] = array[i];
+        }
+        return copy;
+      }
+      @Override double[] copy(double[] array) {
+        int len = array.length;
+        double[] copy = new double[array.length];
+        for (int i = 0; i < len; i++) {
+          copy[i] = array[i];
+        }
+        return copy;
+      }
+      @Override float[] copy(float[] array) {
+        int len = array.length;
+        float[] copy = new float[array.length];
+        for (int i = 0; i < len; i++) {
+          copy[i] = array[i];
+        }
+        return copy;
+      }
+      @Override int[] copy(int[] array) {
+        int len = array.length;
+        int[] copy = new int[array.length];
+        for (int i = 0; i < len; i++) {
+          copy[i] = array[i];
+        }
+        return copy;
+      }
+      @Override long[] copy(long[] array) {
+        int len = array.length;
+        long[] copy = new long[array.length];
+        for (int i = 0; i < len; i++) {
+          copy[i] = array[i];
+        }
+        return copy;
+      }
+      @Override short[] copy(short[] array) {
+        int len = array.length;
+        short[] copy = new short[array.length];
+        for (int i = 0; i < len; i++) {
+          copy[i] = array[i];
+        }
+        return copy;
+      }
+    },
+    ;
+
+    abstract Object[] copy(Object[] array);
+    abstract boolean[] copy(boolean[] array);
+    abstract byte[] copy(byte[] array);
+    abstract char[] copy(char[] array);
+    abstract double[] copy(double[] array);
+    abstract float[] copy(float[] array);
+    abstract int[] copy(int[] array);
+    abstract long[] copy(long[] array);
+    abstract short[] copy(short[] array);
+  }
+
+  @Param Strategy strategy;
+
+  @Param({"5", "500", "50000"}) int size;
+
+  Object[] objectArray;
+  boolean[] booleanArray;
+  byte[] byteArray;
+  char[] charArray;
+  double[] doubleArray;
+  float[] floatArray;
+  int[] intArray;
+  long[] longArray;
+  short[] shortArray;
+
+  @Override protected void setUp() {
+    objectArray = new Object[size];
+    booleanArray = new boolean[size];
+    byteArray = new byte[size];
+    charArray = new char[size];
+    doubleArray = new double[size];
+    floatArray = new float[size];
+    intArray = new int[size];
+    longArray = new long[size];
+    shortArray = new short[size];
+
+    Random random = new Random();
+    for (int i = 0; i < size; i++) {
+      int num = random.nextInt();
+      objectArray[i] = new Object();
+      booleanArray[i] = num % 2 == 0;
+      byteArray[i] = (byte) num;
+      charArray[i] = (char) num;
+      doubleArray[i] = num;
+      floatArray[i] = (float) num;
+      intArray[i] = num;
+      longArray[i] = num;
+      shortArray[i] = (short) num;
+    }
+  }
+
+  public int timeObjects(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.copy(objectArray).hashCode();
+    }
+    return dummy;
+  }
+
+  public int timeBooleans(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.copy(booleanArray).hashCode();
+    }
+    return dummy;
+  }
+
+  public int timeBytes(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.copy(byteArray).hashCode();
+    }
+    return dummy;
+  }
+
+  public int timeChars(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.copy(charArray).hashCode();
+    }
+    return dummy;
+  }
+
+  public int timeDoubles(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.copy(doubleArray).hashCode();
+    }
+    return dummy;
+  }
+
+  public int timeFloats(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.copy(floatArray).hashCode();
+    }
+    return dummy;
+  }
+
+  public int timeInts(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.copy(intArray).hashCode();
+    }
+    return dummy;
+  }
+
+  public int timeLongs(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.copy(longArray).hashCode();
+    }
+    return dummy;
+  }
+
+  public int timeShorts(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.copy(shortArray).hashCode();
+    }
+    return dummy;
+  }
+
+  public static void main(String[] args) {
+    Runner.main(CopyArrayBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/DemoBenchmark.java b/examples/src/main/java/examples/DemoBenchmark.java
new file mode 100644
index 0000000..3b7e2dd
--- /dev/null
+++ b/examples/src/main/java/examples/DemoBenchmark.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.api.Benchmark;
+import com.google.caliper.api.SkipThisScenarioException;
+import com.google.caliper.api.VmParam;
+import com.google.caliper.runner.CaliperMain;
+import com.google.caliper.util.ShortDuration;
+
+import java.math.BigDecimal;
+
+public class DemoBenchmark extends Benchmark {
+  @Param({"abc", "def", "xyz"}) String string;
+  @Param({"1", "2"}) int number;
+  @Param Foo foo;
+
+  @Param({"0.00", "123.45"}) BigDecimal money;
+  @Param({"1ns", "2 minutes"}) ShortDuration duration;
+  @VmParam({"-Xmx32m", "-Xmx1g"}) String memoryMax;
+
+  enum Foo {
+    FOO, BAR, BAZ, QUX;
+  }
+
+  DemoBenchmark() {
+//    System.out.println("I should not do this.");
+  }
+
+  @Override protected void setUp() throws Exception {
+    if (string.equals("abc") && number == 1) {
+      throw new SkipThisScenarioException();
+    }
+  }
+
+  public int timeSomething(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += i;
+    }
+    return dummy;
+  }
+
+  public int timeSomethingElse(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy -= i;
+    }
+    return dummy;
+  }
+
+  @Override protected void tearDown() throws Exception {
+//    System.out.println("Hey, I'm tearing up the joint.");
+  }
+
+  public static void main(String[] args) {
+    CaliperMain.main(DemoBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/DoubleToStringBenchmark.java b/examples/src/main/java/examples/DoubleToStringBenchmark.java
new file mode 100644
index 0000000..291ec84
--- /dev/null
+++ b/examples/src/main/java/examples/DoubleToStringBenchmark.java
@@ -0,0 +1,107 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Measures the various ways the JDK converts doubles to strings.
+ */
+public class DoubleToStringBenchmark extends SimpleBenchmark {
+  @Param Method method;
+
+  public enum Method {
+    TO_STRING {
+      @Override String convert(double d) {
+        return ((Double) d).toString();
+      }
+      @Override String convert(Double d) {
+        return d.toString();
+      }
+    },
+    STRING_VALUE_OF {
+      @Override String convert(double d) {
+        return String.valueOf(d);
+      }
+      @Override String convert(Double d) {
+        return String.valueOf(d);
+      }
+    },
+    STRING_FORMAT {
+      @Override String convert(double d) {
+        return String.format("%f", d);
+      }
+      @Override String convert(Double d) {
+        return String.format("%f", d);
+      }
+    },
+    QUOTE_TRICK {
+      @Override String convert(double d) {
+        return "" + d;
+      }
+      @Override String convert(Double d) {
+        return "" + d;
+      }
+    },
+    ;
+
+    abstract String convert(double d);
+    abstract String convert(Double d);
+  }
+
+  enum Value {
+    Pi(Math.PI),
+    NegativeZero(-0.0),
+    NegativeInfinity(Double.NEGATIVE_INFINITY),
+    NaN(Double.NaN);
+
+    final double value;
+
+    Value(double value) {
+      this.value = value;
+    }
+  }
+
+  @Param Value value;
+
+  public int timePrimitive(int reps) {
+    double d = value.value;
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += method.convert(d).length();
+    }
+    return dummy;
+  }
+
+  public int timeWrapper(int reps) {
+    Double d = value.value;
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      dummy += method.convert(d).length();
+    }
+    return dummy;
+  }
+
+  public static void main(String[] args) throws Exception {
+    Runner.main(DoubleToStringBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/DoubleToStringBenchmark2.java b/examples/src/main/java/examples/DoubleToStringBenchmark2.java
new file mode 100644
index 0000000..8e6c69a
--- /dev/null
+++ b/examples/src/main/java/examples/DoubleToStringBenchmark2.java
@@ -0,0 +1,111 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Measures the various ways the JDK converts doubles to strings.
+ */
+public class DoubleToStringBenchmark2 extends SimpleBenchmark {
+  @Param boolean useWrapper;
+
+  enum Value {
+    Pi(Math.PI),
+    NegativeZero(-0.0),
+    NegativeInfinity(Double.NEGATIVE_INFINITY),
+    NaN(Double.NaN);
+
+    final double d;
+
+    Value(double d) {
+      this.d = d;
+    }
+  }
+
+  @Param Value value;
+
+  public int timeToString(int reps) {
+    int dummy = 0;
+    if (useWrapper) {
+      Double d = value.d;
+      for (int i = 0; i < reps; i++) {
+        dummy += d.toString().length();
+      }
+    } else {
+      double d = value.d;
+      for (int i = 0; i < reps; i++) {
+        dummy += ((Double) d).toString().length();
+      }
+    }
+    return dummy;
+  }
+
+  public int timeStringValueOf(int reps) {
+    int dummy = 0;
+    if (useWrapper) {
+      Double d = value.d;
+      for (int i = 0; i < reps; i++) {
+        dummy += String.valueOf(d).length();
+      }
+    } else {
+      double d = value.d;
+      for (int i = 0; i < reps; i++) {
+        dummy += String.valueOf(d).length();
+      }
+    }
+    return dummy;
+  }
+
+  public int timeStringFormat(int reps) {
+    int dummy = 0;
+    if (useWrapper) {
+      Double d = value.d;
+      for (int i = 0; i < reps; i++) {
+        dummy += String.format("%f", d).length();
+      }
+    } else {
+      double d = value.d;
+      for (int i = 0; i < reps; i++) {
+        dummy += String.format("%f", d).length();
+      }
+    }
+    return dummy;
+  }
+
+  public int timeQuoteTrick(int reps) {
+    int dummy = 0;
+    if (useWrapper) {
+      Double d = value.d;
+      for (int i = 0; i < reps; i++) {
+        dummy += ("" + d).length();
+      }
+    } else {
+      double d = value.d;
+      for (int i = 0; i < reps; i++) {
+        dummy += ("" + d).length();
+      }
+    }
+    return dummy;
+  }
+
+  public static void main(String[] args) throws Exception {
+    Runner.main(DoubleToStringBenchmark2.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/EnumSetContainsBenchmark.java b/examples/src/main/java/examples/EnumSetContainsBenchmark.java
new file mode 100644
index 0000000..b232514
--- /dev/null
+++ b/examples/src/main/java/examples/EnumSetContainsBenchmark.java
@@ -0,0 +1,92 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * Measures EnumSet#contains().
+ */
+public class EnumSetContainsBenchmark extends SimpleBenchmark {
+
+  @Param private SetMaker setMaker;
+
+  public enum SetMaker {
+    ENUM_SET {
+      @Override Set<?> newSet() {
+        return EnumSet.allOf(RegularSize.class);
+      }
+      @Override Object[] testValues() {
+        return new Object[] { RegularSize.E1, RegularSize.E2, RegularSize.E20,
+            RegularSize.E39, RegularSize.E40, "A", LargeSize.E40, null };
+      }
+    },
+    LARGE_ENUM_SET {
+      @Override Set<?> newSet() {
+        return EnumSet.allOf(LargeSize.class);
+      }
+      @Override Object[] testValues() {
+        return new Object[] { LargeSize.E1, LargeSize.E63, LargeSize.E64,
+            LargeSize.E65, LargeSize.E140, "A", RegularSize.E40, null };
+      }
+    };
+
+    abstract Set<?> newSet();
+    abstract Object[] testValues();
+  }
+
+  private enum RegularSize {
+    E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17,
+    E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32,
+    E33, E34, E35, E36, E37, E38, E39, E40,
+  }
+
+  private enum LargeSize {
+    E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17,
+    E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32,
+    E33, E34, E35, E36, E37, E38, E39, E40, E41, E42, E43, E44, E45, E46, E47,
+    E48, E49, E50, E51, E52, E53, E54, E55, E56, E57, E58, E59, E60, E61, E62,
+    E63, E64, E65, E66, E67, E68, E69, E70, E71, E72, E73, E74, E75, E76, E77,
+    E78, E79, E80, E81, E82, E83, E84, E85, E86, E87, E88, E89, E90, E91, E92,
+    E93, E94, E95, E96, E97, E98, E99, E100, E101, E102, E103, E104, E105, E106,
+    E107, E108, E109, E110, E111, E112, E113, E114, E115, E116, E117, E118,
+    E119, E120, E121, E122, E123, E124, E125, E126, E127, E128, E129, E130,
+    E131, E132, E133, E134, E135, E136, E137, E138, E139, E140,
+  }
+
+  private Set<?> set;
+  private Object[] testValues;
+
+  @Override protected void setUp() {
+    this.set = setMaker.newSet();
+    this.testValues = setMaker.testValues();
+  }
+
+  public void timeContains(int reps) {
+    for (int i = 0; i < reps; i++) {
+      set.contains(testValues[i % testValues.length]);
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    Runner.main(EnumSetContainsBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/ExpensiveObjectsBenchmark.java b/examples/src/main/java/examples/ExpensiveObjectsBenchmark.java
new file mode 100644
index 0000000..a11b1bd
--- /dev/null
+++ b/examples/src/main/java/examples/ExpensiveObjectsBenchmark.java
@@ -0,0 +1,76 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.SimpleBenchmark;
+import com.google.caliper.runner.CaliperMain;
+
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+/**
+ * Benchmarks creation and cloning various expensive objects.
+ */
+@SuppressWarnings({"ResultOfObjectAllocationIgnored"}) // TODO: should fix!
+public class ExpensiveObjectsBenchmark extends SimpleBenchmark {
+  public void timeNewDecimalFormatSymbols(int reps) {
+    for (int i = 0; i < reps; ++i) {
+      new DecimalFormatSymbols(Locale.US);
+    }
+  }
+
+  public void timeClonedDecimalFormatSymbols(int reps) {
+    DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
+    for (int i = 0; i < reps; ++i) {
+      dfs.clone();
+    }
+  }
+
+  public void timeNewNumberFormat(int reps) {
+    for (int i = 0; i < reps; ++i) {
+      NumberFormat.getInstance(Locale.US);
+    }
+  }
+
+  public void timeClonedNumberFormat(int reps) {
+    NumberFormat nf = NumberFormat.getInstance(Locale.US);
+    for (int i = 0; i < reps; ++i) {
+      nf.clone();
+    }
+  }
+
+  public void timeNewSimpleDateFormat(int reps) {
+    for (int i = 0; i < reps; ++i) {
+      new SimpleDateFormat();
+    }
+  }
+
+  public void timeClonedSimpleDateFormat(int reps) {
+    SimpleDateFormat sdf = new SimpleDateFormat();
+    for (int i = 0; i < reps; ++i) {
+      sdf.clone();
+    }
+  }
+
+  // TODO: remove this from all examples when IDE plugins are ready
+  public static void main(String[] args) throws Exception {
+    CaliperMain.main(ExpensiveObjectsBenchmark.class, args);
+//    Runner.main(ExpensiveObjectsBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/FormatterBenchmark.java b/examples/src/main/java/examples/FormatterBenchmark.java
new file mode 100644
index 0000000..b4a0541
--- /dev/null
+++ b/examples/src/main/java/examples/FormatterBenchmark.java
@@ -0,0 +1,76 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.Formatter;
+
+/**
+ * Compares Formatter against hand-written StringBuilder code.
+ */
+public class FormatterBenchmark extends SimpleBenchmark {
+  public void timeFormatter_NoFormatting(int reps) {
+    for (int i = 0; i < reps; i++) {
+      Formatter f = new Formatter();
+      f.format("this is a reasonably short string that doesn't actually need any formatting");
+    }
+  }
+
+  public void timeStringBuilder_NoFormatting(int reps) {
+    for (int i = 0; i < reps; i++) {
+      StringBuilder sb = new StringBuilder();
+      sb.append("this is a reasonably short string that doesn't actually need any formatting");
+    }
+  }
+
+  public void timeFormatter_OneInt(int reps) {
+    for (int i = 0; i < reps; i++) {
+      Formatter f = new Formatter();
+      f.format("this is a reasonably short string that has an int %d in it", i);
+    }
+  }
+
+  public void timeStringBuilder_OneInt(int reps) {
+    for (int i = 0; i < reps; i++) {
+      StringBuilder sb = new StringBuilder();
+      sb.append("this is a reasonably short string that has an int ");
+      sb.append(i);
+      sb.append(" in it");
+    }
+  }
+
+  public void timeFormatter_OneString(int reps) {
+    for (int i = 0; i < reps; i++) {
+      Formatter f = new Formatter();
+      f.format("this is a reasonably short string that has a string %s in it", "hello");
+    }
+  }
+
+  public void timeStringBuilder_OneString(int reps) {
+    for (int i = 0; i < reps; i++) {
+      StringBuilder sb = new StringBuilder();
+      sb.append("this is a reasonably short string that has a string ");
+      sb.append("hello");
+      sb.append(" in it");
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    Runner.main(FormatterBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/IntModBenchmark.java b/examples/src/main/java/examples/IntModBenchmark.java
new file mode 100644
index 0000000..55a119c
--- /dev/null
+++ b/examples/src/main/java/examples/IntModBenchmark.java
@@ -0,0 +1,94 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Measures several candidate implementations for mod().
+ */
+@SuppressWarnings("SameParameterValue")
+public class IntModBenchmark extends SimpleBenchmark {
+  private static final int M = (1 << 16) - 1;
+
+  public int timeConditional(int reps) {
+    int dummy = 5;
+    for (int i = 0; i < reps; i++) {
+      dummy += Integer.MAX_VALUE + conditionalMod(dummy, M);
+    }
+    return dummy;
+  }
+
+  private static int conditionalMod(int a, int m) {
+    int r = a % m;
+    return r < 0 ? r + m : r;
+  }
+
+  public int timeDoubleRemainder(int reps) {
+    int dummy = 5;
+    for (int i = 0; i < reps; i++) {
+      dummy += Integer.MAX_VALUE + doubleRemainderMod(dummy, M);
+    }
+    return dummy;
+  }
+
+  @SuppressWarnings("NumericCastThatLosesPrecision") // result of % by an int must be in int range
+  private static int doubleRemainderMod(int a, int m) {
+    return (int) ((a % m + (long) m) % m);
+  }
+
+  public int timeRightShiftingMod(int reps) {
+    int dummy = 5;
+    for (int i = 0; i < reps; i++) {
+      dummy += Integer.MAX_VALUE + rightShiftingMod(dummy, M);
+    }
+    return dummy;
+  }
+
+  @SuppressWarnings("NumericCastThatLosesPrecision") // must be in int range
+  private static int rightShiftingMod(int a, int m) {
+     long r = a % m;
+     return (int) (r + (r >> 63 & m));
+  }
+
+  public int timeLeftShiftingMod(int reps) {
+    int dummy = 5;
+    for (int i = 0; i < reps; i++) {
+      dummy += Integer.MAX_VALUE + leftShiftingMod(dummy, M);
+    }
+    return dummy;
+  }
+
+  @SuppressWarnings("NumericCastThatLosesPrecision") // result of % by an int must be in int range
+  private static int leftShiftingMod(int a, int m) {
+    return (int) ((a + ((long) m << 32)) % m);
+  }
+
+  public int timeWrongMod(int reps) {
+    int dummy = 5;
+    for (int i = 0; i < reps; i++) {
+      dummy += Integer.MAX_VALUE + dummy % M;
+    }
+    return dummy;
+  }
+
+  // TODO: remove this from all examples when IDE plugins are ready
+  public static void main(String[] args) throws Exception {
+    Runner.main(IntModBenchmark.class, args);
+  }
+}
\ No newline at end of file
diff --git a/examples/src/main/java/examples/ListIterationBenchmark.java b/examples/src/main/java/examples/ListIterationBenchmark.java
new file mode 100644
index 0000000..a8cfb05
--- /dev/null
+++ b/examples/src/main/java/examples/ListIterationBenchmark.java
@@ -0,0 +1,73 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.AbstractList;
+import java.util.List;
+
+/**
+ * Measures iterating through list elements.
+ */
+public class ListIterationBenchmark extends SimpleBenchmark {
+
+  @Param({"0", "10", "100", "1000"})
+  private int length;
+
+  private List<Object> list;
+  private Object[] array;
+
+  @Override protected void setUp() {
+    array = new Object[length];
+    for (int i = 0; i < length; i++) {
+      array[i] = new Object();
+    }
+
+    list = new AbstractList<Object>() {
+      @Override public int size() {
+        return length;
+      }
+
+      @Override public Object get(int i) {
+        return array[i];
+      }
+    };
+  }
+
+  @SuppressWarnings({"UnusedDeclaration"}) // TODO: fix
+  public void timeListIteration(int reps) {
+    for (int i = 0; i < reps; i++) {
+      for (Object value : list) {
+      }
+    }
+  }
+
+  @SuppressWarnings({"UnusedDeclaration"}) // TODO: fix
+  public void timeArrayIteration(int reps) {
+    for (int i = 0; i < reps; i++) {
+      for (Object value : array) {
+      }
+    }
+  }
+
+  // TODO: remove this from all examples when IDE plugins are ready
+  public static void main(String[] args) throws Exception {
+    Runner.main(ListIterationBenchmark.class, args);
+  }
+}
\ No newline at end of file
diff --git a/examples/src/main/java/examples/LoopingBackwardsBenchmark.java b/examples/src/main/java/examples/LoopingBackwardsBenchmark.java
new file mode 100644
index 0000000..1e3d1ad
--- /dev/null
+++ b/examples/src/main/java/examples/LoopingBackwardsBenchmark.java
@@ -0,0 +1,52 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Testing the old canard that looping backwards is faster.
+ */
+public class LoopingBackwardsBenchmark extends SimpleBenchmark {
+  @Param({"2", "20", "2000", "20000000"}) int max;
+
+  public int timeForwards(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      for (int j = 0; j < max; j++) {
+        dummy += j;
+      }
+    }
+    return dummy;
+  }
+
+  public int timeBackwards(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      for (int j = max - 1; j >= 0; j--) {
+        dummy += j;
+      }
+    }
+    return dummy;
+  }
+
+  public static void main(String[] args) throws Exception {
+    Runner.main(LoopingBackwardsBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/MessageDigestCreationBenchmark.java b/examples/src/main/java/examples/MessageDigestCreationBenchmark.java
new file mode 100644
index 0000000..c9437bd
--- /dev/null
+++ b/examples/src/main/java/examples/MessageDigestCreationBenchmark.java
@@ -0,0 +1,44 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.security.MessageDigest;
+
+/**
+ * Times creating new MessageDigest instances.
+ */
+public class MessageDigestCreationBenchmark extends SimpleBenchmark {
+  // By default, just the "interesting ones". Also consider Adler32 and CRC32,
+  // but these are not guaranteed to be supported in all runtime environments.
+  @Param({"MD5", "SHA-1", "SHA-256", "SHA-512"})
+  String algorithm;
+
+  public void time(int reps) throws Exception {
+    // Change this to use a dummy if the results look suspicious.
+    for (int i = 0; i < reps; i++) {
+      MessageDigest.getInstance(algorithm);
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    Runner.main(MessageDigestCreationBenchmark.class, args);
+  }
+}
diff --git a/examples/src/main/java/examples/StringBuilderBenchmark.java b/examples/src/main/java/examples/StringBuilderBenchmark.java
new file mode 100644
index 0000000..3c839d3
--- /dev/null
+++ b/examples/src/main/java/examples/StringBuilderBenchmark.java
@@ -0,0 +1,132 @@
+/*
+ * 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 examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Tests the performance of various StringBuilder methods.
+ */
+public class StringBuilderBenchmark extends SimpleBenchmark {
+
+    @Param({"1", "10", "100"}) private int length;
+
+    public void timeAppendBoolean(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(true);
+            }
+        }
+    }
+
+    public void timeAppendChar(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append('c');
+            }
+        }
+    }
+
+    public void timeAppendCharArray(int reps) {
+        char[] chars = "chars".toCharArray();
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(chars);
+            }
+        }
+    }
+
+    public void timeAppendCharSequence(int reps) {
+        CharSequence cs = "chars";
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(cs);
+            }
+        }
+    }
+
+    public void timeAppendDouble(int reps) {
+        double d = 1.2;
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(d);
+            }
+        }
+    }
+
+    public void timeAppendFloat(int reps) {
+        float f = 1.2f;
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(f);
+            }
+        }
+    }
+
+    public void timeAppendInt(int reps) {
+        int n = 123;
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(n);
+            }
+        }
+    }
+
+    public void timeAppendLong(int reps) {
+        long l = 123;
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(l);
+            }
+        }
+    }
+
+    public void timeAppendObject(int reps) {
+        Object o = new Object();
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(o);
+            }
+        }
+    }
+
+    public void timeAppendString(int reps) {
+        String s = "chars";
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(s);
+            }
+        }
+    }
+
+    // TODO: remove this from all examples when IDE plugins are ready
+    public static void main(String[] args) throws Exception {
+        Runner.main(StringBuilderBenchmark.class, args);
+    }
+}
diff --git a/lib/gson-1.7.1.jar b/lib/gson-1.7.1.jar
new file mode 100644
index 0000000..eb54274
--- /dev/null
+++ b/lib/gson-1.7.1.jar
Binary files differ
diff --git a/lib/java-allocation-instrumenter-2.0.jar b/lib/java-allocation-instrumenter-2.0.jar
new file mode 100644
index 0000000..8ea4193
--- /dev/null
+++ b/lib/java-allocation-instrumenter-2.0.jar
Binary files differ
diff --git a/scripts/caliper b/scripts/caliper
new file mode 100644
index 0000000..a25caf1
--- /dev/null
+++ b/scripts/caliper
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# rough
+
+export PATH=$PATH:$JAVA_HOME/bin
+base=`dirname $0`
+export ALLOCATION_JAR=$base/lib/allocation.jar
+exec java -cp $base/lib/caliper-@VERSION@.jar:$ALLOCATION_JAR:$CLASSPATH com.google.caliper.Runner $*
+
diff --git a/tutorial/Tutorial.java b/tutorial/Tutorial.java
new file mode 100644
index 0000000..625d387
--- /dev/null
+++ b/tutorial/Tutorial.java
@@ -0,0 +1,187 @@
+/*
+ * 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 tutorial;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Caliper tutorial. To run the example benchmarks in this file:
+ * {@code CLASSPATH=... [caliper_home]/caliper tutorial.Tutorial.Benchmark1}
+ */
+public class Tutorial {
+
+  /*
+   * We begin the Caliper tutorial with the simplest benchmark you can write.
+   * We'd like to know how efficient the method System.nanoTime() is.
+   *
+   * Notice:
+   *
+   *  - We write a class that extends com.google.caliper.SimpleBenchmark.
+   *  - It contains a public instance method whose name begins with 'time' and
+   *    which accepts a single 'int reps' parameter.
+   *  - The body of the method simply executes the code we wish to measure,
+   *    'reps' times.
+   *
+   * Example run:
+   *
+   *    $ CLASSPATH=build/classes/test caliper tutorial.Tutorial.Benchmark1
+   *    [real-time results appear on this line]
+   *
+   *    Summary report for tutorial.Tutorial$Benchmark1:
+   *
+   *    Benchmark   ns
+   *    ---------  ---
+   *    NanoTime   233
+   */
+  public static class Benchmark1 extends SimpleBenchmark {
+    public void timeNanoTime(int reps) {
+      for (int i = 0; i < reps; i++) {
+        System.nanoTime();
+      }
+    }
+  }
+
+  /*
+   * Now let's compare two things: nanoTime() versus currentTimeMillis().
+   * Notice:
+   *
+   *  - We simply add another method, following the same rules as the first.
+   *
+   * Example run output:
+   *
+   *   Benchmark           ns
+   *   -----------------  ---
+   *   NanoTime           248
+   *   CurrentTimeMillis  118
+   */
+  public static class Benchmark2 extends SimpleBenchmark {
+    public void timeNanoTime(int reps) {
+      for (int i = 0; i < reps; i++) {
+        System.nanoTime();
+      }
+    }
+    public void timeCurrentTimeMillis(int reps) {
+      for (int i = 0; i < reps; i++) {
+        System.currentTimeMillis();
+      }
+    }
+  }
+
+  /*
+   * Let's try iterating over a large array. This seems simple enough, but
+   * there is a problem!
+   */
+  public static class Benchmark3 extends SimpleBenchmark {
+    private final int[] array = new int[1000000];
+
+    @SuppressWarnings("UnusedDeclaration") // IDEA tries to warn us!
+    public void timeArrayIteration_BAD(int reps) {
+      for (int i = 0; i < reps; i++) {
+        for (int ignoreMe : array) {}
+      }
+    }
+  }
+
+  /*
+   * Caliper reported that the benchmark above ran in 4 nanoseconds.
+   *
+   * Wait, what?
+   *
+   * How can it possibly iterate over a million zeroes in 4 ns!?
+   *
+   * It is very important to sanity-check benchmark results with common sense!
+   * In this case, we're indeed getting a bogus result. The problem is that the
+   * Java Virtual Machine is too smart: it detected the fact that the loop was
+   * producing no actual result, so it simply compiled it right out. The method
+   * never looped at all. To fix this, we need to use a dummy result value.
+   *
+   * Notice:
+   *
+   *  - We simply change the 'time' method from 'void' to any return type we
+   *    wish. Then we return a value that can't be known without actually
+   *    performing the work, and thus we defeat the runtime optimizations.
+   *  - We're no longer timing *just* the code we want to be testing - our
+   *    result will now be inflated by the (small) cost of addition. This is an
+   *    unfortunate fact of life with microbenchmarking. In fact, we were
+   *    already inflated by the cost of an int comparison, "i < reps" as it was.
+   *
+   * With this change, Caliper should report a much more realistic value, more
+   * on the order of an entire millisecond.
+   */
+  public static class Benchmark4 extends SimpleBenchmark {
+    private final int[] array = new int[1000000];
+
+    public int timeArrayIteration_fixed(int reps) {
+      int dummy = 0;
+      for (int i = 0; i < reps; i++) {
+        for (int doNotIgnoreMe : array) {
+          dummy += doNotIgnoreMe;
+        }
+      }
+      return dummy; // framework ignores this, but it has served its purpose!
+    }
+  }
+
+  /*
+   * Now we'd like to know how various other *sizes* of arrays perform. We
+   * don't want to have to cut and paste the whole benchmark just to provide a
+   * different size. What we need is a parameter!
+   *
+   * When you run this benchmark the same way you ran the previous ones, you'll
+   * now get an error: "No values provided for benchmark parameter 'size'".
+   * You can provide the value requested at the command line like this:
+   *
+   *   [caliper_home]/caliper tutorial.Tutorial.Benchmark5 -Dsize=100}
+   *
+   * You'll see output like this:
+   *
+   *   Benchmark       size   ns
+   *   --------------  ----  ---
+   *   ArrayIteration   100   51
+   *
+   * Now that we've parameterized our benchmark, things are starting to get fun.
+   * Try passing '-Dsize=10,100,1000' and see what happens!
+   *
+   *   Benchmark       size   ns
+   *   --------------  ----  -----------------------------------
+   *   ArrayIteration    10    7 |
+   *   ArrayIteration   100   49 ||||
+   *   ArrayIteration  1000  477 ||||||||||||||||||||||||||||||
+   *
+   */
+  public static class Benchmark5 extends SimpleBenchmark {
+    @Param int size; // set automatically by framework
+
+    private int[] array; // set by us, in setUp()
+
+    @Override protected void setUp() {
+      // @Param values are guaranteed to have been injected by now
+      array = new int[size];
+    }
+
+    public int timeArrayIteration(int reps) {
+      int dummy = 0;
+      for (int i = 0; i < reps; i++) {
+        for (int doNotIgnoreMe : array) {
+          dummy += doNotIgnoreMe;
+        }
+      }
+      return dummy;
+    }
+  }
+}