Upgrade to latest (at the time of writing) version of Caliper
am: e236301e5f

* commit 'e236301e5fc778bffe1748ed80d7936e6c807012':
  Upgrade to latest (at the time of writing) version of Caliper
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c4e65ae
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+*.iml
+*.ipr
+*.iws
+.idea
+target
+*~
diff --git a/Android.mk b/Android.mk
index 1207464..ee45b62 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,6 +14,9 @@
 
 LOCAL_PATH := $(call my-dir)
 
+# Include definitions of DAGGER2_PROCESSOR_CLASSES/LIBRARIES
+include external/dagger2/dagger2_annotation_processor.mk
+
 # build caliper host jar
 # ============================================================
 
@@ -24,16 +27,55 @@
 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_IS_HOST_MODULE := true
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
+  apache-commons-math-host \
   caliper-gson-host \
   caliper-java-allocation-instrumenter-host \
+  caliper-jersey-client-host \
+  caliper-jersey-core-host \
+  caliper-joda-time-host \
+  caliper-jsr311-api-host \
+  dagger2-host \
+  dagger2-inject-host \
   guavalib
 
+# Use Dagger2 annotation processor
+PROCESSOR_LIBRARIES := $(DAGGER2_PROCESSOR_LIBRARIES)
+PROCESSOR_CLASSES := $(DAGGER2_PROCESSOR_CLASSES)
+include external/dagger2/java_annotation_processors.mk
+
 include $(BUILD_HOST_JAVA_LIBRARY)
 
+# Remember the location of the generated files, this is needed for when
+# building for target
+caliper_host_generated_sources_dir := $(local-generated-sources-dir)/annotation_processor_output
+
+# build caliper target api jar
+# ============================================================
+# This contains just those classes needed for benchmarks to compile.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := caliper-api-target
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_SRC_FILES := \
+  caliper/src/main/java/com/google/caliper/AfterExperiment.java \
+  caliper/src/main/java/com/google/caliper/BeforeExperiment.java \
+  caliper/src/main/java/com/google/caliper/Param.java \
+  caliper/src/main/java/com/google/caliper/All.java \
+  caliper/src/main/java/com/google/caliper/Benchmark.java
+
+include $(BUILD_JAVA_LIBRARY)
+
 # build caliper tests
 # ============================================================
+# vogar --expectations $ANDROID_BUILD_TOP/external/caliper/expectations/knownfailures.txt \
+        --test-only \
+        --classpath $ANDROID_BUILD_TOP/out/host/common/obj/JAVA_LIBRARIES/caliper-tests_intermediates/javalib.jar \
+        com.google.caliper
 
 include $(CLEAR_VARS)
 
@@ -41,19 +83,49 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 LOCAL_SRC_FILES := $(call all-java-files-under, caliper/src/test/java/)
+LOCAL_JAVA_RESOURCE_DIRS := caliper/src/test/resources
+LOCAL_IS_HOST_MODULE := true
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
   caliper-host \
-  junit
+  junit \
+  mockito-host
+
+# Use Dagger2 annotation processor
+PROCESSOR_LIBRARIES := $(DAGGER2_PROCESSOR_LIBRARIES)
+PROCESSOR_CLASSES := $(DAGGER2_PROCESSOR_CLASSES)
+include external/dagger2/java_annotation_processors.mk
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
-# Build dependencies.
+# build caliper examples
+# ============================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := caliper-examples
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_SRC_FILES := $(call all-java-files-under, examples/src/main/java/)
+LOCAL_IS_HOST_MODULE := true
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+  caliper-host \
+  junit \
+  mockito-host
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build host dependencies.
 # ============================================================
 include $(CLEAR_VARS)
 
 LOCAL_PREBUILT_JAVA_LIBRARIES := \
-    caliper-gson-host:lib/gson-1.7.1$(COMMON_JAVA_PACKAGE_SUFFIX) \
-    caliper-java-allocation-instrumenter-host:lib/java-allocation-instrumenter-2.0$(COMMON_JAVA_PACKAGE_SUFFIX)
+    caliper-gson-host:lib/gson-2.2.2$(COMMON_JAVA_PACKAGE_SUFFIX) \
+    caliper-java-allocation-instrumenter-host:lib/java-allocation-instrumenter-2.0$(COMMON_JAVA_PACKAGE_SUFFIX) \
+    caliper-jersey-client-host:lib/jersey-client-1.11$(COMMON_JAVA_PACKAGE_SUFFIX) \
+    caliper-jersey-core-host:lib/jersey-core-1.11$(COMMON_JAVA_PACKAGE_SUFFIX) \
+    caliper-joda-time-host:lib/joda-time-2.1$(COMMON_JAVA_PACKAGE_SUFFIX) \
+    caliper-jsr311-api-host:lib/jsr311-api-1.1.1$(COMMON_JAVA_PACKAGE_SUFFIX)
 
 include $(BUILD_HOST_PREBUILT)
diff --git a/README.android b/README.android
index 91617b5..850d2ee 100644
--- a/README.android
+++ b/README.android
@@ -2,9 +2,14 @@
 License: Apache 2
 Description: "Google's Caliper Benchmarking And Measuring Tool"
 
-Version: 0.5-rc1
+Version: 73efbe138dafba57d6a890257961ba83f41b89f2
 
-Some of the examples do not build, that is an issue with upstream.
+This uses the Dagger2 dependency injection framework which runs as an annotation
+processor and generates the dependency injecting code. At the moment the
+generated code for the caliper-host target can be found at:
+  out/host/common/gen/JAVA_LIBRARIES/caliper-host_intermediates/annotation_processor_output/
+For the caliper-tests target it can be found at:
+  out/host/common/gen/JAVA_LIBRARIES/caliper-tests_intermediates/annotation_processor_output/
 
 Local Patches:
     None
diff --git a/caliper/pom.xml b/caliper/pom.xml
index befd583..9884724 100644
--- a/caliper/pom.xml
+++ b/caliper/pom.xml
@@ -1,22 +1,57 @@
-<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">
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 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.
+  -->
+
+<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>
+
+  <groupId>com.google.caliper</groupId>
+  <artifactId>caliper</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>caliper</name>
   <description>Caliper: Microbenchmarking Framework for Java</description>
-  <properties>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-  </properties>
+
+  <url>http://code.google.com/p/caliper/</url>
+
+  <inceptionYear>2009</inceptionYear>
+
+  <organization>
+    <name>Google Inc.</name>
+    <url>http://www.google.com</url>
+  </organization>
+
+  <developers>
+    <developer>
+      <name>Gregory Kick</name>
+      <organization>Google Inc.</organization>
+    </developer>
+    <developer>
+      <name>Jesse Wilson</name>
+    </developer>
+  </developers>
+
   <licenses>
     <license>
       <name>The Apache Software License, Version 2.0</name>
@@ -24,55 +59,97 @@
       <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>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <gpg.skip>true</gpg.skip>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>com.google.code.findbugs</groupId>
       <artifactId>jsr305</artifactId>
       <version>1.3.9</version>
+      <scope>provided</scope><!-- used only for annotations -->
     </dependency>
     <dependency>
       <groupId>com.google.code.gson</groupId>
       <artifactId>gson</artifactId>
-      <version>1.7.1</version>
+      <version>2.2.2</version>
+    </dependency>
+    <dependency>
+      <groupId>joda-time</groupId>
+      <artifactId>joda-time</artifactId>
+      <version>2.1</version>
+    </dependency>
+    <dependency>
+      <groupId>com.sun.jersey</groupId>
+      <artifactId>jersey-client</artifactId>
+      <version>1.11</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.dagger</groupId>
+      <artifactId>dagger</artifactId>
+      <version>2.1-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.dagger</groupId>
+      <artifactId>dagger-compiler</artifactId>
+      <version>2.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
-      <version>11.0.1</version>
-      <scope>compile</scope>
+      <version>18.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.hibernate</groupId>
+      <artifactId>hibernate-core</artifactId>
+      <version>3.6.7.Final</version>
+      <scope>provided</scope><!-- used only for annotations -->
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-math</artifactId>
+      <version>2.2</version>
     </dependency>
     <dependency>
       <groupId>com.google.code.java-allocation-instrumenter</groupId>
       <artifactId>java-allocation-instrumenter</artifactId>
-      <version>2.0</version>
+      <version>3.0</version>
     </dependency>
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
-      <version>3.8.2</version>
+      <version>4.10</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <version>1.9.5</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>
+        <version>3.2</version>
         <configuration>
           <source>1.6</source>
           <target>1.6</target>
@@ -80,8 +157,13 @@
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>2.5</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-eclipse-plugin</artifactId>
-        <version>2.8</version>
+        <version>2.9</version>
         <configuration>
           <downloadSources>true</downloadSources>
           <downloadJavadocs>true</downloadJavadocs>
@@ -90,12 +172,19 @@
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-release-plugin</artifactId>
+        <version>2.5.1</version>
+        <configuration>
+          <arguments>-DenableCiProfile=true</arguments>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-source-plugin</artifactId>
-        <version>2.1.2</version>
+        <version>2.4</version>
         <executions>
           <execution>
             <id>attach-sources</id>
-            <phase>verify</phase>
             <goals>
               <goal>jar-no-fork</goal>
             </goals>
@@ -105,7 +194,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.7</version>
+        <version>2.10.1</version>
         <executions>
           <execution>
             <id>generate-javadocs</id>
@@ -117,18 +206,91 @@
         </executions>
         <configuration>
           <links>
-            <link>http://download.oracle.com/javase/1.5.0/docs/api/</link>
+            <link>http://download.oracle.com/javase/1.6.0/docs/api/</link>
           </links>
           <version>true</version>
           <show>public</show>
         </configuration>
       </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>2.3</version>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <shadedArtifactAttached>true</shadedArtifactAttached>
+              <shadedClassifierName>all</shadedClassifierName>
+              <!-- disable relocation as it is breaking some reflection -->
+              <!-- <relocations>
+                <relocation>
+                  <pattern>com.google</pattern>
+                  <shadedPattern>com.google.caliper.shaded.com.google</shadedPattern>
+                  <excludes>
+                    <exclude>com.google.caliper.**</exclude>
+                  </excludes>
+                </relocation>
+                <relocation>
+                  <pattern>com.sun.jersey</pattern>
+                  <shadedPattern>com.google.caliper.shaded.com.sun.jersey</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>com.sun.ws</pattern>
+                  <shadedPattern>com.google.caliper.shaded.com.sun.ws</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>javax.inject</pattern>
+                  <shadedPattern>com.google.caliper.shaded.javax.inject</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>javax.ws</pattern>
+                  <shadedPattern>com.google.caliper.shaded.javax.ws</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>org.aopalliance</pattern>
+                  <shadedPattern>com.google.caliper.shaded.org.aopalliance</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>org.joda</pattern>
+                  <shadedPattern>com.google.caliper.shaded.org.joda</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>org.objectweb</pattern>
+                  <shadedPattern>com.google.caliper.shaded.org.objectweb</shadedPattern>
+                </relocation>
+              </relocations> -->
+              <transformers>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                  <manifestEntries>
+                    <Premain-Class>com.google.monitoring.runtime.instrumentation.AllocationInstrumenter</Premain-Class>
+                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
+                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
+                  </manifestEntries>
+                </transformer>
+              </transformers>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-gpg-plugin</artifactId>
+        <version>1.4</version>
+        <executions>
+          <execution>
+            <id>sign-artifacts</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>sign</goal>
+            </goals>
+          </execution>
+        </executions>
+      </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/AfterExperiment.java b/caliper/src/main/java/com/google/caliper/AfterExperiment.java
new file mode 100644
index 0000000..19f6cf7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/AfterExperiment.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 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 java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation for methods to be run after an experiment has been performed.
+ *
+ * @see BeforeExperiment
+ */
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface AfterExperiment {
+  /**
+   * A qualifier for which types of experiments this method should run. For example, annotating a
+   * method with {@code @AfterExperiment(Benchmark.class)} will cause it to only run for
+   * {@link Benchmark} experiments. By default, annotated methods run for all experiments.
+   */
+  Class<? extends Annotation> value() default All.class;
+}
diff --git a/caliper/src/main/java/com/google/caliper/UploadResults.java b/caliper/src/main/java/com/google/caliper/All.java
similarity index 70%
rename from caliper/src/main/java/com/google/caliper/UploadResults.java
rename to caliper/src/main/java/com/google/caliper/All.java
index 7dae7af..08b2456 100644
--- a/caliper/src/main/java/com/google/caliper/UploadResults.java
+++ b/caliper/src/main/java/com/google/caliper/All.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2013 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,13 +16,11 @@
 
 package com.google.caliper;
 
-import java.io.File;
+import java.lang.annotation.Target;
 
 /**
- * Usage: UploadResults <file_or_dir>
+ * The default value of lifecycle annotations that indicates it should associate with all benchmark
+ * types.
  */
-public class UploadResults {
-  public static void main(String[] args) {
-    new Runner().uploadResultsFileOrDir(new File(args[0]));
-  }
-}
+@Target({}) // should never actually be applied
+public @interface All {}
diff --git a/caliper/src/main/java/com/google/caliper/AllocationMeasurer.java b/caliper/src/main/java/com/google/caliper/AllocationMeasurer.java
deleted file mode 100644
index 2ec3488..0000000
--- a/caliper/src/main/java/com/google/caliper/AllocationMeasurer.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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
deleted file mode 100644
index cceb079..0000000
--- a/caliper/src/main/java/com/google/caliper/Arguments.java
+++ /dev/null
@@ -1,420 +0,0 @@
-/*
- * 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/BeforeExperiment.java b/caliper/src/main/java/com/google/caliper/BeforeExperiment.java
new file mode 100644
index 0000000..ec67f4c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/BeforeExperiment.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 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 java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation for methods to be run before an experiment has been performed.
+ *
+ * @see AfterExperiment
+ */
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface BeforeExperiment {
+  /**
+   * A qualifier for which types of experiments this method should run. For example, annotating a
+   * method with {@code @BeforeExperiment(Benchmark.class)} will cause it to only run for
+   * {@link Benchmark} experiments. By default, annotated methods run for all experiments.
+   */
+  Class<? extends Annotation> value() default All.class;
+}
diff --git a/caliper/src/main/java/com/google/caliper/Benchmark.java b/caliper/src/main/java/com/google/caliper/Benchmark.java
index 06bda82..252017c 100644
--- a/caliper/src/main/java/com/google/caliper/Benchmark.java
+++ b/caliper/src/main/java/com/google/caliper/Benchmark.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 Google Inc.
+ * Copyright (C) 2013 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,40 +16,64 @@
 
 package com.google.caliper;
 
-import java.util.Map;
-import java.util.Set;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
 
 /**
- * A collection of benchmarks that share a set of configuration parameters.
+ * Annotation for benchmark methods. To write a benchmark:
+ *
+ * <ol>
+ *   <li>Annotate one or more methods with this annotation.
+ *   <li>Annotate any fields with {@literal @}{@link Param} that should have parameter values
+ *       injected (see {@literal @}{@link Param} for more details)
+ *   <li>Optionally use {@link BeforeExperiment} and {@link AfterExperiment} on setup and teardown
+ *       methods
+ * </ol>
+ *
+ * <p>Since many benchmarks may execute in a shorter duration than is accurately measured by
+ * available timers, benchmark methods <i>may</i> take either an {@code int} or {@code long}
+ * argument representing a number of repetitions to perform in a given execution. It is critical
+ * that the work done in the benchmark method scale linearly to the number of repetitions.
+ *
+ * <p>Benchmark methods may return any value. It will be ignored.
+ *
+ * <p>This class is instantiated and injected only once per child VM invocation, to measure one
+ * particular combination of parameters.
+ *
+ * <p>For example: <pre>   {@code
+ *   public final class MyBenchmark {
+ *     {@literal @}Param FeatureEnum feature;
+ *     {@literal @}Param({"1", "10", "100"}) int size;
+ *     private MyObject objectToBenchmark;
+ *
+ *     {@literal @}BeforeExperiment void initializeObject() {
+ *       objectToBenchmark = new MyObject(size);
+ *     }
+ *
+ *     {@literal @}Benchmark int foo(int reps) {
+ *       MyObject object = objectToBenchmark;  // copy to local to avoid field access overhead
+ *       int dummy = 0;
+ *       for (int i = 0; i < reps; i++) {
+ *         dummy += object.foo(feature);
+ *       }
+ *       // return a dummy value so the JIT compiler doesn't optimize away the entire method.
+ *       return dummy;
+ *     }
+ *
+ *     {@literal @}Benchmark int bar() {
+ *       // benchmark another operation of MyObject that doesn't require a reps parameter
+ *     }
+ *   }
+ * </pre>
+ *
+ * <p>The benchmark class MyBenchmark has two benchmark methods ({@code foo} and {@code bar}) and
+ * two {@link Param Params} ({@code feature} and {@code size}). For each experiment performed by
+ * Caliper (e.g. {@code foo} with {@code feature == FeatureEnum.A} and {@code size == 100}),
+ * {@code initializeObject} will be called exactly once, but {@code foo} may be called many times.
  */
-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
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface Benchmark {}
diff --git a/caliper/src/main/java/com/google/caliper/CaliperRc.java b/caliper/src/main/java/com/google/caliper/CaliperRc.java
deleted file mode 100644
index 21a283f..0000000
--- a/caliper/src/main/java/com/google/caliper/CaliperRc.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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
deleted file mode 100644
index c4a35ec..0000000
--- a/caliper/src/main/java/com/google/caliper/ConfigurationException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-/**
- * Thrown upon occurrence of a 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
deleted file mode 100644
index f150ec1..0000000
--- a/caliper/src/main/java/com/google/caliper/ConfiguredBenchmark.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-import 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
deleted file mode 100644
index 8449bda..0000000
--- a/caliper/src/main/java/com/google/caliper/ConsoleReport.java
+++ /dev/null
@@ -1,427 +0,0 @@
-/**
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-import com.google.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
deleted file mode 100644
index 5c19a61..0000000
--- a/caliper/src/main/java/com/google/caliper/CountingPrintStream.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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
deleted file mode 100644
index f89cc28..0000000
--- a/caliper/src/main/java/com/google/caliper/DalvikVm.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 64d4b7f..0000000
--- a/caliper/src/main/java/com/google/caliper/DebugMeasurer.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 75c2813..0000000
--- a/caliper/src/main/java/com/google/caliper/Environment.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 9b7150b..0000000
--- a/caliper/src/main/java/com/google/caliper/EnvironmentGetter.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 1ef4788..0000000
--- a/caliper/src/main/java/com/google/caliper/InProcessRunner.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 3fe89c7..0000000
--- a/caliper/src/main/java/com/google/caliper/InstancesAllocationMeasurer.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 9e3f809..0000000
--- a/caliper/src/main/java/com/google/caliper/Json.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 09acebe..0000000
--- a/caliper/src/main/java/com/google/caliper/LogConstants.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 35865b6..0000000
--- a/caliper/src/main/java/com/google/caliper/Measurement.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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
deleted file mode 100644
index eb0b42a..0000000
--- a/caliper/src/main/java/com/google/caliper/MeasurementSet.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * 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/Measurer.java b/caliper/src/main/java/com/google/caliper/Measurer.java
deleted file mode 100644
index 93002e6..0000000
--- a/caliper/src/main/java/com/google/caliper/Measurer.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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
deleted file mode 100644
index a28aabf..0000000
--- a/caliper/src/main/java/com/google/caliper/MemoryAllocationMeasurer.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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
index f4cdf45..82e3471 100644
--- a/caliper/src/main/java/com/google/caliper/Param.java
+++ b/caliper/src/main/java/com/google/caliper/Param.java
@@ -29,12 +29,6 @@
  * <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.
@@ -47,7 +41,7 @@
  *
  * <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. 
+ * six independent "scenarios" and perform measurement for each one.
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.FIELD)
diff --git a/caliper/src/main/java/com/google/caliper/Parameter.java b/caliper/src/main/java/com/google/caliper/Parameter.java
deleted file mode 100644
index c79bc86..0000000
--- a/caliper/src/main/java/com/google/caliper/Parameter.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-import com.google.common.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
deleted file mode 100644
index c57f5a3..0000000
--- a/caliper/src/main/java/com/google/caliper/Result.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 2396dd3..0000000
--- a/caliper/src/main/java/com/google/caliper/ResultsReader.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 00f9350..0000000
--- a/caliper/src/main/java/com/google/caliper/Run.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-import com.google.common.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
deleted file mode 100644
index 57715c9..0000000
--- a/caliper/src/main/java/com/google/caliper/Runner.java
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-import com.google.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
deleted file mode 100644
index bc026d8..0000000
--- a/caliper/src/main/java/com/google/caliper/Scenario.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-import com.google.common.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
deleted file mode 100644
index 1fa687d..0000000
--- a/caliper/src/main/java/com/google/caliper/ScenarioResult.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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
deleted file mode 100644
index fcfead0..0000000
--- a/caliper/src/main/java/com/google/caliper/ScenarioSelection.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 01fc3a8..0000000
--- a/caliper/src/main/java/com/google/caliper/SimpleBenchmark.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-import com.google.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
deleted file mode 100644
index 3366eb1..0000000
--- a/caliper/src/main/java/com/google/caliper/StandardVm.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 2925d2d..0000000
--- a/caliper/src/main/java/com/google/caliper/TimeMeasurer.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/**
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-import 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
deleted file mode 100644
index c3268e0..0000000
--- a/caliper/src/main/java/com/google/caliper/TypeConverter.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-import com.google.common.collect.ImmutableMap;
-
-import java.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/UserException.java b/caliper/src/main/java/com/google/caliper/UserException.java
deleted file mode 100644
index a4535a7..0000000
--- a/caliper/src/main/java/com/google/caliper/UserException.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.caliper;
-
-import 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
deleted file mode 100644
index 1cc45a9..0000000
--- a/caliper/src/main/java/com/google/caliper/Vm.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 408e34c..0000000
--- a/caliper/src/main/java/com/google/caliper/VmFactory.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 2aeebda..0000000
--- a/caliper/src/main/java/com/google/caliper/Xml.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 458e6fa..0000000
--- a/caliper/src/main/java/com/google/caliper/XmlUtils.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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/api/AfterRep.java b/caliper/src/main/java/com/google/caliper/api/AfterRep.java
new file mode 100644
index 0000000..628edcc
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/api/AfterRep.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 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.api;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.annotations.Beta;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Apply this annotation to any method without parameters to have it run after each rep of a
+ * {@link Macrobenchmark}.
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+@Beta
+public @interface AfterRep {}
diff --git a/caliper/src/main/java/com/google/caliper/api/BeforeRep.java b/caliper/src/main/java/com/google/caliper/api/BeforeRep.java
new file mode 100644
index 0000000..b4ae203
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/api/BeforeRep.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 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.api;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.annotations.Beta;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Apply this annotation to any method without parameters to have it run before each rep of a
+ * {@link Macrobenchmark}.
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+@Beta
+public @interface BeforeRep {}
diff --git a/caliper/src/main/java/com/google/caliper/api/Footprint.java b/caliper/src/main/java/com/google/caliper/api/Footprint.java
new file mode 100644
index 0000000..0fa7b8c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/api/Footprint.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.api;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a method whose return value is an object whose total memory footprint is to be
+ * measured.
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface Footprint {
+  /**
+   * Optionally ignore instances of the specified types (including subclasses) when measuring.  For
+   * example, {@code @Footprint(ignore = Element.class) public Set<Element> set() {...}} would
+   * measure the size of the set while ignoring the size of the elements.
+   */
+  Class<?>[] exclude() default {};
+}
diff --git a/caliper/src/main/java/com/google/caliper/api/Macrobenchmark.java b/caliper/src/main/java/com/google/caliper/api/Macrobenchmark.java
new file mode 100644
index 0000000..aac4763
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/api/Macrobenchmark.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 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.api;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.annotations.Beta;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Apply this annotation to any method without parameters to have it timed as a macrobenchmark. A
+ * macrobenchmark is roughly defined as any benchmark whose runtime is large enough that the
+ * granularity of the {@linkplain System#nanoTime clock} is not a factor in measurement. Thus, each
+ * repetition of the benchmark code can be timed individually.
+ *
+ * <p>Additionally, since each rep is independently timed, setup and tear down logic can be
+ * performed in between each using the {@link BeforeRep} and {@link AfterRep} annotations
+ * respectively.
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+@Beta
+public @interface Macrobenchmark {}
diff --git a/caliper/src/main/java/com/google/caliper/api/ResultProcessor.java b/caliper/src/main/java/com/google/caliper/api/ResultProcessor.java
new file mode 100644
index 0000000..1cc0ee0
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/api/ResultProcessor.java
@@ -0,0 +1,31 @@
+/*
+ * 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.api;
+
+import com.google.caliper.model.Trial;
+
+import java.io.Closeable;
+
+/**
+ * Interface for processing results as they complete. Callers must invoke {@link #close()} after
+ * all trials have been {@linkplain #processTrial processed}.
+ *
+ * <p>Implementations must have a public no argument constructor.
+ */
+public interface ResultProcessor extends Closeable {
+  void processTrial(Trial trial);
+}
diff --git a/caliper/src/main/java/com/google/caliper/api/SkipThisScenarioException.java b/caliper/src/main/java/com/google/caliper/api/SkipThisScenarioException.java
new file mode 100644
index 0000000..90b6fc1
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/api/SkipThisScenarioException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.api;
+
+/**
+ * Throw this exception from your benchmark class's setUp method, or benchmark method
+ * to indicate that the combination of parameters supplied should not be benchmarked. For example,
+ * while you might want to test <i>most</i> combinations of the parameters {@code size} and {@code
+ * comparator}, you might not want to test the specific combination of {@code size=100000000} and
+ * {@code comparator=reallyExpensiveComparator}.
+ */
+@SuppressWarnings("serial")
+public final class SkipThisScenarioException extends RuntimeException {}
diff --git a/caliper/src/main/java/com/google/caliper/api/VmOptions.java b/caliper/src/main/java/com/google/caliper/api/VmOptions.java
new file mode 100644
index 0000000..db73da8
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/api/VmOptions.java
@@ -0,0 +1,38 @@
+/*
+ * 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.api;
+
+import com.google.common.annotations.Beta;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation applied to a benchmark that specifies flags to be applied to the VM. These flags
+ * are applied before those specified on the command-line and thus are not guaranteed to be applied.
+ *
+ * <p>This API is likely to change.
+ */
+// TODO(gak): Support platform (e.g. Android and Jvm) specific options.
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Beta
+public @interface VmOptions {
+  String[] value() default {};
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/AbstractLogMessageVisitor.java b/caliper/src/main/java/com/google/caliper/bridge/AbstractLogMessageVisitor.java
new file mode 100644
index 0000000..f38e09c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/AbstractLogMessageVisitor.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 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.bridge;
+
+/**
+ * An abstract {@link LogMessageVisitor} with no-op implementations for the visit methods -
+ * provided so that all visitors don't have to override all methods.
+ */
+public abstract class AbstractLogMessageVisitor implements LogMessageVisitor {
+  @Override public void visit(GcLogMessage logMessage) {}
+
+  @Override public void visit(FailureLogMessage logMessage) {}
+
+  @Override public void visit(HotspotLogMessage logMessage) {}
+
+  @Override public void visit(StartMeasurementLogMessage logMessage) {}
+
+  @Override public void visit(StopMeasurementLogMessage logMessage) {}
+
+  @Override public void visit(VmOptionLogMessage logMessage) {}
+
+  @Override public void visit(VmPropertiesLogMessage logMessage) {}
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/BridgeModule.java b/caliper/src/main/java/com/google/caliper/bridge/BridgeModule.java
new file mode 100644
index 0000000..0d6ddea
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/BridgeModule.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 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.bridge;
+
+import com.google.caliper.util.Parser;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Bindings for {@link Parser parsers} for {@link com.google.caliper.model model} classes.
+ */
+@Module
+public final class BridgeModule {
+  @Provides static Parser<LogMessage> provideLogMessageParser(LogMessageParser parser) {
+    return parser;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/CommandLineSerializer.java b/caliper/src/main/java/com/google/caliper/bridge/CommandLineSerializer.java
new file mode 100644
index 0000000..f063355
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/CommandLineSerializer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 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.bridge;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.io.BaseEncoding;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * Serializes and deserializes WorkerSpecs as base64 encoded strings so they can be passed on the
+ * command line to the worker.
+ *
+ * <p>Java serialization is a appropriate in this usecase because there are no compatibility
+ * concerns.  The messages encoded/decoded by this class are only used to communicate with another
+ * JVM that is running with the same version of the java classes.  Also, it should be lighter weight
+ * and faster than other common serialization solutions.
+ */
+public final class CommandLineSerializer {
+  private CommandLineSerializer() {}
+
+  /** Returns the given serializable object as a base64 encoded String. */
+  public static String render(WorkerSpec message) {
+    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+    try {
+      ObjectOutputStream out = new ObjectOutputStream(bytes);
+      out.writeObject(message);
+      out.close();
+      return BaseEncoding.base64().encode(bytes.toByteArray());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /** Parses the given base64 encoded string as an object of the given type. */
+  public static WorkerSpec parse(String arg) {
+    try {
+      ByteArrayInputStream bais = new ByteArrayInputStream(BaseEncoding.base64().decode(arg));
+      ObjectInputStream in = new ObjectInputStream(bais);
+      WorkerSpec instance = (WorkerSpec) in.readObject();
+      in.close();
+      checkState(bais.read() == -1,
+          "Message %s contains more than one object.", arg);
+      return instance;
+    } catch (IOException e) {
+      throw new RuntimeException(e);  // assertion error?
+    } catch (ClassNotFoundException e) {
+      throw new RuntimeException("cannot decode message", e);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/FailureLogMessage.java b/caliper/src/main/java/com/google/caliper/bridge/FailureLogMessage.java
new file mode 100644
index 0000000..4321a67
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/FailureLogMessage.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 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.bridge;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Throwables;
+
+import java.io.Serializable;
+
+/**
+ * A message containing information on a failure encountered by the worker JVM.
+ */
+public class FailureLogMessage extends LogMessage implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private final String stackTrace;
+
+  public FailureLogMessage(Throwable e) {
+    this(Throwables.getStackTraceAsString(e));
+  }
+
+  public FailureLogMessage(String stackTrace) {
+    this.stackTrace = checkNotNull(stackTrace);
+  }
+
+  public String stackTrace() {
+    return stackTrace;
+  }
+
+  @Override
+  public void accept(LogMessageVisitor visitor) {
+    visitor.visit(this);
+  }
+
+  @Override
+  public int hashCode() {
+    return stackTrace.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof FailureLogMessage) {
+      FailureLogMessage that = (FailureLogMessage) obj;
+      return this.stackTrace.equals(that.stackTrace);
+    } else {
+      return false;
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/GcLogMessage.java b/caliper/src/main/java/com/google/caliper/bridge/GcLogMessage.java
new file mode 100644
index 0000000..2d59788
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/GcLogMessage.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 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.bridge;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.caliper.util.ShortDuration;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+/**
+ * A message representing output produced by the JVM when {@code -XX:+PrintGC} is enabled.
+ */
+public final class GcLogMessage extends LogMessage {
+  /**
+   * The type of the garbage collection performed.
+   */
+  public static enum Type {
+    FULL,
+    INCREMENTAL,
+  }
+
+  private final Type type;
+  private final ShortDuration duration;
+
+  GcLogMessage(Type type, ShortDuration duration) {
+    this.type = checkNotNull(type);
+    this.duration = checkNotNull(duration);
+  }
+
+  public Type type() {
+    return type;
+  }
+
+  public ShortDuration duration() {
+    return duration;
+  }
+
+  @Override
+  public void accept(LogMessageVisitor visitor) {
+    visitor.visit(this);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(type, duration);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof GcLogMessage) {
+      GcLogMessage that = (GcLogMessage) obj;
+      return this.type == that.type
+          && this.duration.equals(that.duration);
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .addValue(type)
+        .add("duration", duration)
+        .toString();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/UploadResults.java b/caliper/src/main/java/com/google/caliper/bridge/HotspotLogMessage.java
similarity index 62%
copy from caliper/src/main/java/com/google/caliper/UploadResults.java
copy to caliper/src/main/java/com/google/caliper/bridge/HotspotLogMessage.java
index 7dae7af..0670153 100644
--- a/caliper/src/main/java/com/google/caliper/UploadResults.java
+++ b/caliper/src/main/java/com/google/caliper/bridge/HotspotLogMessage.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2012 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
-
-import java.io.File;
+package com.google.caliper.bridge;
 
 /**
- * Usage: UploadResults <file_or_dir>
+ * A message representing output produced by the JVM when {@code -XX:+PrintCompliation} is enabled.
  */
-public class UploadResults {
-  public static void main(String[] args) {
-    new Runner().uploadResultsFileOrDir(new File(args[0]));
+public final class HotspotLogMessage extends LogMessage {
+  HotspotLogMessage() {}
+
+  @Override
+  public void accept(LogMessageVisitor visitor) {
+    visitor.visit(this);
   }
 }
diff --git a/caliper/src/main/java/com/google/caliper/MeasurementType.java b/caliper/src/main/java/com/google/caliper/bridge/LogMessage.java
similarity index 70%
copy from caliper/src/main/java/com/google/caliper/MeasurementType.java
copy to caliper/src/main/java/com/google/caliper/bridge/LogMessage.java
index 30de69f..fc4a2ad 100644
--- a/caliper/src/main/java/com/google/caliper/MeasurementType.java
+++ b/caliper/src/main/java/com/google/caliper/bridge/LogMessage.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2012 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
+package com.google.caliper.bridge;
 
-import com.google.common.annotations.GwtCompatible;
-
-@GwtCompatible
-public enum MeasurementType {
-  TIME, INSTANCE, MEMORY, DEBUG
+/**
+ * A single line of output produced by a {@code Worker}.
+ */
+public abstract class LogMessage {
+  public abstract void accept(LogMessageVisitor visitor);
 }
diff --git a/caliper/src/main/java/com/google/caliper/bridge/LogMessageParser.java b/caliper/src/main/java/com/google/caliper/bridge/LogMessageParser.java
new file mode 100644
index 0000000..68ab785
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/LogMessageParser.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2013 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.bridge;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.caliper.util.Parser;
+import com.google.caliper.util.ShortDuration;
+
+import java.math.BigDecimal;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.inject.Inject;
+
+/**
+ * Parses {@link LogMessage} strings.
+ */
+final class LogMessageParser implements Parser<LogMessage> {
+  @Inject LogMessageParser() {}
+
+  private static final Pattern GC_PATTERN =
+      Pattern.compile(".*\\[(?:(Full) )?GC.*(\\d+\\.\\d+) secs\\]");
+  private static final Pattern JIT_PATTERN =
+      Pattern.compile(".*::.*( \\(((\\d+ bytes)|(static))\\))?");
+  private static final Pattern VM_OPTION_PATTERN =
+      Pattern.compile("\\s*(\\w+)\\s+(\\w+)\\s+:?=\\s+([^\\s]*)\\s+\\{([^}]*)\\}\\s*");
+
+  @Override public LogMessage parse(CharSequence text) {
+    // TODO(gak): do this stuff in terms of CharSequence instead of String
+    String string = text.toString();
+    Matcher gcMatcher = GC_PATTERN.matcher(string);
+    if (gcMatcher.matches()) {
+      return new GcLogMessage(
+          "Full".equals(gcMatcher.group(1))
+              ? GcLogMessage.Type.FULL
+              : GcLogMessage.Type.INCREMENTAL,
+          ShortDuration.of(BigDecimal.valueOf(Double.parseDouble(gcMatcher.group(2))), SECONDS));
+    }
+    Matcher jitMatcher = JIT_PATTERN.matcher(string);
+    if (jitMatcher.matches()) {
+      return new HotspotLogMessage();
+    }
+    Matcher vmOptionMatcher = VM_OPTION_PATTERN.matcher(string);
+    if (vmOptionMatcher.matches()) {
+      return new VmOptionLogMessage(vmOptionMatcher.group(2), vmOptionMatcher.group(3));
+    }
+    return null;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/LogMessageVisitor.java b/caliper/src/main/java/com/google/caliper/bridge/LogMessageVisitor.java
new file mode 100644
index 0000000..f91bbf1
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/LogMessageVisitor.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2012 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.bridge;
+
+/**
+ * A visitor for the various subclasses of {@link LogMessage}.
+ */
+public interface LogMessageVisitor {
+  void visit(GcLogMessage logMessage);
+  void visit(FailureLogMessage logMessage);
+  void visit(HotspotLogMessage logMessage);
+  void visit(StartMeasurementLogMessage logMessage);
+  void visit(StopMeasurementLogMessage logMessage);
+  void visit(VmOptionLogMessage logMessage);
+  void visit(VmPropertiesLogMessage logMessage);
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/OpenedSocket.java b/caliper/src/main/java/com/google/caliper/bridge/OpenedSocket.java
new file mode 100644
index 0000000..510d9e6
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/OpenedSocket.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2014 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.bridge;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.Flushable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.net.Socket;
+import java.nio.channels.SocketChannel;
+
+/**
+ * A simple tuple for the opened streams of a socket.
+ */
+public final class OpenedSocket {
+  /**
+   * Waits for the channel to connect and returns a new {@link OpenedSocket}.
+   */
+  public static OpenedSocket fromSocket(SocketChannel socket) throws IOException {
+    socket.configureBlocking(true);
+    socket.finishConnect();
+    return fromSocket(socket.socket());
+  }
+  /**
+   * Returns a new {@link OpenedSocket} for the given connected {@link Socket} instance.
+   */
+  public static OpenedSocket fromSocket(Socket socket) throws IOException {
+    // Setting this to true disables Nagle's algorithm (RFC 896) which seeks to decrease packet
+    // overhead by buffering writes while there are packets outstanding (i.e. haven't been ack'd).
+    // This interacts poorly with another TCP feature called 'delayed acks' (RFC 1122) if the
+    // application sends lots of small messages (which we do!).  Without this enabled messages sent
+    // by the worker may be delayed by up to the delayed ack timeout (on linux this is 40-500ms,
+    // though in practice I have only observed 40ms).  So we need to enable the TCP_NO_DELAY option
+    // here.
+    socket.setTcpNoDelay(true);
+    // N.B. order is important here, constructing an ObjectOutputStream requires writing a header
+    // and constructing an ObjectInputStream requires reading that header.  So we always need to
+    // construct the OOS first so we don't deadlock. 
+    ObjectOutputStream output = new ObjectOutputStream(getOutputStream(socket));
+    ObjectInputStream input = new ObjectInputStream(getInputStream(socket));
+    return new OpenedSocket(new Reader(input), new Writer(output));
+  }
+  
+  private final Reader reader;
+  private final Writer writer;
+
+  private OpenedSocket(Reader reader,
+      Writer objectOutputStream) {
+    this.reader = reader;
+    this.writer = objectOutputStream;
+  }
+
+  public Reader reader() {
+    return reader;
+  }
+  
+  public Writer writer() {
+    return writer;
+  }
+  
+  /** Reads objects from the socket. */
+  public static final class Reader implements Closeable {
+    private final ObjectInputStream input;
+
+    Reader(ObjectInputStream is) {
+      this.input = is;
+    }
+    
+    /** Returns the next object, or {@code null} if we are at EOF. */
+    public Serializable read() throws IOException {
+      try {
+        return (Serializable) checkNotNull(input.readObject());
+      } catch (EOFException eof) {
+        // TODO(lukes): The only 'better' way to handle this would be to use an explicit poison pill
+        // marker in the stream.  Otherwise we just have to catch EOFException.
+        return null;
+      } catch (ClassNotFoundException e) {
+        throw new AssertionError(e);
+      }
+    }
+
+    @Override public void close() throws IOException  {
+      input.close();
+    }
+  }
+
+  /** Writes objects to the socket. */
+  public static final class Writer implements Closeable, Flushable {
+    private final ObjectOutputStream output;
+
+    Writer(ObjectOutputStream output) {
+      this.output = output;
+    }
+
+    /** Returns the next object, or {@code null} if we are at EOF. */
+    public void write(Serializable serializable) throws IOException {
+      output.writeObject(serializable);
+    }
+
+    @Override public void flush() throws IOException {
+      output.flush();
+    }
+
+    @Override public void close() throws IOException {
+      output.close();
+    }
+  }
+  
+  /**
+   * Returns an {@link OutputStream} for the socket, but unlike {@link Socket#getOutputStream()}
+   * when you call {@link OutputStream#close() close} it only closes the output end of the socket
+   * instead of the entire socket.
+   */
+  private static OutputStream getOutputStream(final Socket socket) throws IOException {
+    final OutputStream delegate = socket.getOutputStream();
+    return new OutputStream() {
+
+      @Override public void close() throws IOException {
+        delegate.flush();
+        synchronized (socket) {
+          socket.shutdownOutput();
+          if (socket.isInputShutdown()) {
+            socket.close();
+          }
+        }
+      }
+    
+      // Boring delegates from here on down
+      @Override public void write(int b) throws IOException {
+        delegate.write(b);
+      }
+    
+      @Override public void write(byte[] b) throws IOException {
+        delegate.write(b);
+      }
+    
+      @Override public void write(byte[] b, int off, int len) throws IOException {
+        delegate.write(b, off, len);
+      }
+    
+      @Override public void flush() throws IOException {
+        delegate.flush();
+      }
+    };
+  }
+  
+  /**
+   * Returns an {@link InputStream} for the socket, but unlike {@link Socket#getInputStream()}
+   * when you call {@link InputStream#close() close} it only closes the input end of the socket
+   * instead of the entire socket.
+   */
+  private static InputStream getInputStream(final Socket socket) throws IOException {
+    final InputStream delegate = socket.getInputStream();
+    return new InputStream() {
+      @Override public void close() throws IOException {
+        synchronized (socket) {
+          socket.shutdownInput();
+          if (socket.isOutputShutdown()) {
+            socket.close();
+          }
+        }
+      }
+      
+      // Boring delegates from here on down
+      @Override public int read() throws IOException {
+        return delegate.read();
+      }
+      
+      @Override public int read(byte[] b) throws IOException {
+        return delegate.read(b);
+      }
+      
+      @Override public int read(byte[] b, int off, int len) throws IOException {
+        return delegate.read(b, off, len);
+      }
+      
+      @Override public long skip(long n) throws IOException {
+        return delegate.skip(n);
+      }
+      
+      @Override public int available() throws IOException {
+        return delegate.available();
+      }
+      
+      @Override public void mark(int readlimit) {
+        delegate.mark(readlimit);
+      }
+      
+      @Override public void reset() throws IOException {
+        delegate.reset();
+      }
+      
+      @Override public boolean markSupported() {
+        return delegate.markSupported();
+      }
+    };
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/MeasurementType.java b/caliper/src/main/java/com/google/caliper/bridge/Renderer.java
similarity index 74%
copy from caliper/src/main/java/com/google/caliper/MeasurementType.java
copy to caliper/src/main/java/com/google/caliper/bridge/Renderer.java
index 30de69f..f2c9037 100644
--- a/caliper/src/main/java/com/google/caliper/MeasurementType.java
+++ b/caliper/src/main/java/com/google/caliper/bridge/Renderer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2012 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
+package com.google.caliper.bridge;
 
-import com.google.common.annotations.GwtCompatible;
-
-@GwtCompatible
-public enum MeasurementType {
-  TIME, INSTANCE, MEMORY, DEBUG
+public interface Renderer<T> {
+  String render(T object);
 }
diff --git a/caliper/src/main/java/com/google/caliper/bridge/ShouldContinueMessage.java b/caliper/src/main/java/com/google/caliper/bridge/ShouldContinueMessage.java
new file mode 100644
index 0000000..114f5f6
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/ShouldContinueMessage.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 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.bridge;
+
+import java.io.Serializable;
+
+/**
+ * A message sent from the runner to the worker to indicate whether or not measuring should 
+ * continue.
+ */
+public class ShouldContinueMessage implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private final boolean shouldContinue;
+  private final boolean warmupComplete;
+
+  public ShouldContinueMessage(boolean shouldContinue, boolean warmupComplete) {
+    this.shouldContinue = shouldContinue;
+    this.warmupComplete = warmupComplete;
+  }
+  
+  public boolean shouldContinue() {
+    return shouldContinue;
+  }
+  
+  @Override public int hashCode() {
+    return Boolean.valueOf(shouldContinue).hashCode();
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj instanceof ShouldContinueMessage 
+        && shouldContinue == ((ShouldContinueMessage) obj).shouldContinue;
+  }
+
+  public boolean isWarmupComplete() {
+    return warmupComplete;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/StartMeasurementLogMessage.java b/caliper/src/main/java/com/google/caliper/bridge/StartMeasurementLogMessage.java
new file mode 100644
index 0000000..e04dcd5
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/StartMeasurementLogMessage.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 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.bridge;
+
+import java.io.Serializable;
+
+/**
+ * A message signaling that the timing interval has started in the worker.
+ */
+// TODO(gak): rename in terms of measurement
+public class StartMeasurementLogMessage extends LogMessage implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  @Override public void accept(LogMessageVisitor visitor) {
+    visitor.visit(this);
+  }
+
+  @Override public int hashCode() {
+    return StartMeasurementLogMessage.class.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    return obj instanceof StartMeasurementLogMessage;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/StartupAnnounceMessage.java b/caliper/src/main/java/com/google/caliper/bridge/StartupAnnounceMessage.java
new file mode 100644
index 0000000..7efd9b3
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/StartupAnnounceMessage.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 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.bridge;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * A message sent from the worker to the runner immediately after startup to identify the trial
+ * that it is performing.
+ */
+public final class StartupAnnounceMessage implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private final UUID trialId;
+
+  public StartupAnnounceMessage(UUID trialId) {
+    this.trialId = checkNotNull(trialId);
+  }
+
+  public UUID trialId() {
+    return trialId;
+  }
+
+  @Override public int hashCode() {
+    return trialId.hashCode();
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj instanceof StartupAnnounceMessage
+        && trialId.equals(((StartupAnnounceMessage) obj).trialId);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/StopMeasurementLogMessage.java b/caliper/src/main/java/com/google/caliper/bridge/StopMeasurementLogMessage.java
new file mode 100644
index 0000000..b93ee50
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/StopMeasurementLogMessage.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 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.bridge;
+
+import com.google.caliper.model.Measurement;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+
+import java.io.Serializable;
+
+/**
+ * A message signaling that the timing interval has ended in the worker.
+ */
+public class StopMeasurementLogMessage extends LogMessage implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private final ImmutableList<Measurement> measurements;
+
+  public StopMeasurementLogMessage(Iterable<Measurement> measurements) {
+    this.measurements = ImmutableList.copyOf(measurements);
+  }
+
+  public ImmutableList<Measurement> measurements() {
+    return measurements;
+  }
+
+  @Override public void accept(LogMessageVisitor visitor) {
+    visitor.visit(this);
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(measurements);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof StopMeasurementLogMessage) {
+      StopMeasurementLogMessage that = (StopMeasurementLogMessage) obj;
+      return this.measurements.equals(that.measurements);
+    } else {
+      return false;
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/VmOptionLogMessage.java b/caliper/src/main/java/com/google/caliper/bridge/VmOptionLogMessage.java
new file mode 100644
index 0000000..dd8d5cd
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/VmOptionLogMessage.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 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.bridge;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+
+/**
+ * A message representing output produced by the JVM when {@code -XX:+PrintFlagsFinal} is enabled.
+ */
+public final class VmOptionLogMessage extends LogMessage {
+  private final String name;
+  private final String value;
+
+  VmOptionLogMessage(String name, String value) {
+    this.name = checkNotNull(name);
+    this.value = checkNotNull(value);
+  }
+
+  public String name() {
+    return name;
+  }
+
+  public String value() {
+    return value;
+  }
+
+  @Override
+  public void accept(LogMessageVisitor visitor) {
+    visitor.visit(this);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/VmPropertiesLogMessage.java b/caliper/src/main/java/com/google/caliper/bridge/VmPropertiesLogMessage.java
new file mode 100644
index 0000000..2e63879
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/VmPropertiesLogMessage.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 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.bridge;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+import java.io.Serializable;
+
+/**
+ * A message containing a selection of {@link System#getProperties() system properties} from the
+ * worker JVM.
+ */
+public class VmPropertiesLogMessage extends LogMessage implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private final ImmutableMap<String, String> properties;
+
+  public VmPropertiesLogMessage() {
+    this(ImmutableMap.copyOf(Maps.fromProperties(System.getProperties())));
+  }
+
+  public VmPropertiesLogMessage(ImmutableMap<String, String> properties) {
+    this.properties = checkNotNull(properties);
+  }
+
+  public ImmutableMap<String, String> properties() {
+    return properties;
+  }
+
+  @Override public void accept(LogMessageVisitor visitor) {
+    visitor.visit(this);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(properties);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof VmPropertiesLogMessage) {
+      VmPropertiesLogMessage that = (VmPropertiesLogMessage) obj;
+      return this.properties.equals(that.properties);
+    } else {
+      return false;
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/bridge/WorkerSpec.java b/caliper/src/main/java/com/google/caliper/bridge/WorkerSpec.java
new file mode 100644
index 0000000..022b1dc
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/bridge/WorkerSpec.java
@@ -0,0 +1,59 @@
+/*
+ * 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.bridge;
+
+import com.google.caliper.model.BenchmarkSpec;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * This object is sent from the parent process to the child to tell it what to do. If the child
+ * does not do it, it will not get its allowance this week.
+ */
+public final class WorkerSpec implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  public final UUID trialId;
+  // Should be ? extends Worker but circular build deps prevent that.
+  public final Class<?> workerClass;
+  public final ImmutableMap<String, String> workerOptions;
+  public final BenchmarkSpec benchmarkSpec;
+
+  /**
+   * The names of the benchmark method parameters so that the method can be uniquely identified.
+   */
+  public final ImmutableList<Class<?>> methodParameterClasses;
+  public final int port;
+
+  public WorkerSpec(
+      UUID trialId,
+      Class<?> workerClass,
+      ImmutableMap<String, String> workerOptions,
+      BenchmarkSpec benchmarkSpec,
+      ImmutableList<Class<?>> methodParameterClasses,
+      int port) {
+    this.trialId = trialId;
+    this.workerClass = workerClass;
+    this.workerOptions = workerOptions;
+    this.benchmarkSpec = benchmarkSpec;
+    this.methodParameterClasses = methodParameterClasses;
+    this.port = port;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/MeasurementType.java b/caliper/src/main/java/com/google/caliper/bridge/package-info.java
similarity index 73%
copy from caliper/src/main/java/com/google/caliper/MeasurementType.java
copy to caliper/src/main/java/com/google/caliper/bridge/package-info.java
index 30de69f..2d474ee 100644
--- a/caliper/src/main/java/com/google/caliper/MeasurementType.java
+++ b/caliper/src/main/java/com/google/caliper/bridge/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2012 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,7 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
-
-import com.google.common.annotations.GwtCompatible;
-
-@GwtCompatible
-public enum MeasurementType {
-  TIME, INSTANCE, MEMORY, DEBUG
-}
+/**
+ * Bridges between the runner and the worker.
+ */
+package com.google.caliper.bridge;
\ No newline at end of file
diff --git a/caliper/src/main/java/com/google/caliper/config/CaliperConfig.java b/caliper/src/main/java/com/google/caliper/config/CaliperConfig.java
new file mode 100644
index 0000000..3be0ade
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/config/CaliperConfig.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2012 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.config;
+
+import static com.google.caliper.util.Util.subgroupMap;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.caliper.api.ResultProcessor;
+import com.google.caliper.platform.Platform;
+import com.google.caliper.platform.VirtualMachineException;
+import com.google.caliper.util.Util;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+/**
+ * Represents caliper configuration.  By default, {@code ~/.caliper/config.properties} and
+ * {@code global-config.properties}.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class CaliperConfig {
+  @VisibleForTesting final ImmutableMap<String, String> properties;
+  private final ImmutableMap<Class<? extends ResultProcessor>, ResultProcessorConfig>
+      resultProcessorConfigs;
+
+  @VisibleForTesting
+  public CaliperConfig(ImmutableMap<String, String> properties)
+      throws InvalidConfigurationException {
+    this.properties = checkNotNull(properties);
+    this.resultProcessorConfigs = findResultProcessorConfigs(subgroupMap(properties, "results"));
+  }
+
+  private static final Pattern CLASS_PROPERTY_PATTERN = Pattern.compile("(\\w+)\\.class");
+
+  private static <T> ImmutableBiMap<String, Class<? extends T>> mapGroupNamesToClasses(
+      ImmutableMap<String, String> groupProperties, Class<T> type)
+          throws InvalidConfigurationException {
+    BiMap<String, Class<? extends T>> namesToClasses = HashBiMap.create();
+    for (Entry<String, String> entry : groupProperties.entrySet()) {
+      Matcher matcher = CLASS_PROPERTY_PATTERN.matcher(entry.getKey());
+      if (matcher.matches() && !entry.getValue().isEmpty()) {
+        try {
+          Class<?> someClass = Util.loadClass(entry.getValue());
+          checkState(type.isAssignableFrom(someClass));
+          @SuppressWarnings("unchecked")
+          Class<? extends T> verifiedClass = (Class<? extends T>) someClass;
+          namesToClasses.put(matcher.group(1), verifiedClass);
+        } catch (ClassNotFoundException e) {
+          throw new InvalidConfigurationException("Cannot find result processor class: "
+              + entry.getValue());
+        }
+      }
+    }
+    return ImmutableBiMap.copyOf(namesToClasses);
+  }
+
+  private static ImmutableMap<Class<? extends ResultProcessor>, ResultProcessorConfig>
+      findResultProcessorConfigs(ImmutableMap<String, String> resultsProperties)
+          throws InvalidConfigurationException {
+    ImmutableBiMap<String, Class<? extends ResultProcessor>> processorToClass =
+        mapGroupNamesToClasses(resultsProperties, ResultProcessor.class);
+    ImmutableMap.Builder<Class<? extends ResultProcessor>, ResultProcessorConfig> builder =
+        ImmutableMap.builder();
+    for (Entry<String, Class<? extends ResultProcessor>> entry : processorToClass.entrySet()) {
+      builder.put(entry.getValue(), getResultProcessorConfig(resultsProperties, entry.getKey()));
+    }
+    return builder.build();
+  }
+
+  public ImmutableMap<String, String> properties() {
+    return properties;
+  }
+
+  /**
+   * Returns the configuration of the current host VM (including the flags used to create it). Any
+   * args specified using {@code vm.args} will also be applied
+   */
+  public VmConfig getDefaultVmConfig(Platform platform) {
+    return new VmConfig.Builder(platform, platform.defaultVmHomeDir())
+        .addAllOptions(platform.inputArguments())
+        // still incorporate vm.args
+        .addAllOptions(getArgs(subgroupMap(properties, "vm")))
+        .build();
+  }
+
+  public VmConfig getVmConfig(Platform platform, String name)
+      throws InvalidConfigurationException {
+    checkNotNull(name);
+    ImmutableMap<String, String> vmGroupMap = subgroupMap(properties, "vm");
+    ImmutableMap<String, String> vmMap = subgroupMap(vmGroupMap, name);
+    File homeDir;
+    try {
+      homeDir = platform.customVmHomeDir(vmGroupMap, name);
+    } catch (VirtualMachineException e) {
+      throw new InvalidConfigurationException(e);
+    }
+    return new VmConfig.Builder(platform, homeDir)
+        .addAllOptions(getArgs(vmGroupMap))
+        .addAllOptions(getArgs(vmMap))
+        .build();
+  }
+
+  private static final Pattern INSTRUMENT_CLASS_PATTERN = Pattern.compile("([^\\.]+)\\.class");
+
+  public ImmutableSet<String> getConfiguredInstruments() {
+    ImmutableSet.Builder<String> resultBuilder = ImmutableSet.builder();
+    for (String key : subgroupMap(properties, "instrument").keySet()) {
+      Matcher matcher = INSTRUMENT_CLASS_PATTERN.matcher(key);
+      if (matcher.matches()) {
+        resultBuilder.add(matcher.group(1));
+      }
+    }
+    return resultBuilder.build();
+  }
+
+  public InstrumentConfig getInstrumentConfig(String name) {
+    checkNotNull(name);
+    ImmutableMap<String, String> instrumentGroupMap = subgroupMap(properties, "instrument");
+    ImmutableMap<String, String> insrumentMap = subgroupMap(instrumentGroupMap, name);
+    @Nullable String className = insrumentMap.get("class");
+    checkArgument(className != null, "no instrument configured named %s", name);
+    return new InstrumentConfig.Builder()
+        .className(className)
+        .addAllOptions(subgroupMap(insrumentMap, "options"))
+        .build();
+  }
+
+  public ImmutableSet<Class<? extends ResultProcessor>> getConfiguredResultProcessors() {
+    return resultProcessorConfigs.keySet();
+  }
+
+  public ResultProcessorConfig getResultProcessorConfig(
+      Class<? extends ResultProcessor> resultProcessorClass) {
+    return resultProcessorConfigs.get(resultProcessorClass);
+  }
+
+  private static ResultProcessorConfig getResultProcessorConfig(
+      ImmutableMap<String, String> resultsProperties, String name) {
+    ImmutableMap<String, String> resultsMap = subgroupMap(resultsProperties, name);
+    return new ResultProcessorConfig.Builder()
+        .className(resultsMap.get("class"))
+        .addAllOptions(subgroupMap(resultsMap, "options"))
+        .build();
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("properties", properties)
+        .toString();
+  }
+
+  private static List<String> getArgs(Map<String, String> properties) {
+    String argsString = Strings.nullToEmpty(properties.get("args"));
+    ImmutableList.Builder<String> args = ImmutableList.builder();
+    StringBuilder arg = new StringBuilder();
+    for (int i = 0; i < argsString.length(); i++) {
+      char c = argsString.charAt(i);
+      switch (c) {
+        case '\\':
+          arg.append(argsString.charAt(++i));
+          break;
+        case ' ':
+          if (arg.length() > 0) {
+            args.add(arg.toString());
+          }
+          arg = new StringBuilder();
+          break;
+        default:
+          arg.append(c);
+          break;
+      }
+    }
+    if (arg.length() > 0) {
+      args.add(arg.toString());
+    }
+    return args.build();
+  }
+
+}
diff --git a/caliper/src/main/java/com/google/caliper/config/CaliperConfigLoader.java b/caliper/src/main/java/com/google/caliper/config/CaliperConfigLoader.java
new file mode 100644
index 0000000..a103cf2
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/config/CaliperConfigLoader.java
@@ -0,0 +1,98 @@
+/*
+ * 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.config;
+
+import com.google.caliper.options.CaliperOptions;
+import com.google.caliper.util.Util;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteSource;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Loads caliper configuration files and, if necessary, creates new versions from the defaults.
+ */
+@Singleton
+public final class CaliperConfigLoader {
+  private final CaliperOptions options;
+
+  @Inject CaliperConfigLoader(CaliperOptions options) {
+    this.options = options;
+  }
+
+  public CaliperConfig loadOrCreate() throws InvalidConfigurationException {
+    File configFile = options.caliperConfigFile();
+    ImmutableMap<String, String> defaults;
+    try {
+      defaults = Util.loadProperties(
+          Util.resourceSupplier(CaliperConfig.class, "global-config.properties"));
+    } catch (IOException impossible) {
+      throw new AssertionError(impossible);
+    }
+
+    // TODO(kevinb): deal with migration issue from old-style .caliperrc
+
+    if (configFile.exists()) {
+      try {
+        ImmutableMap<String, String> user =
+            Util.loadProperties(Files.asByteSource(configFile));
+        return new CaliperConfig(mergeProperties(options.configProperties(), user, defaults));
+      } catch (IOException keepGoing) {
+      }
+    }
+
+    ByteSource supplier = Util.resourceSupplier(CaliperConfig.class, "default-config.properties");
+    tryCopyIfNeeded(supplier, configFile);
+
+    ImmutableMap<String, String> user;
+    try {
+      user = Util.loadProperties(supplier);
+    } catch (IOException e) {
+      throw new AssertionError(e); // class path must be messed up
+    }
+    return new CaliperConfig(mergeProperties(options.configProperties(), user, defaults));
+  }
+
+  private static ImmutableMap<String, String> mergeProperties(Map<String, String> commandLine,
+      Map<String, String> user,
+      Map<String, String> defaults) {
+    Map<String, String> map = Maps.newHashMap(defaults);
+    map.putAll(user); // overwrite and augment
+    map.putAll(commandLine); // overwrite and augment
+    Iterables.removeIf(map.values(), Predicates.equalTo(""));
+    return ImmutableMap.copyOf(map);
+  }
+
+  private static void tryCopyIfNeeded(ByteSource supplier, File rcFile) {
+    if (!rcFile.exists()) {
+      try {
+        supplier.copyTo(Files.asByteSink(rcFile));
+      } catch (IOException e) {
+        rcFile.delete();
+      }
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/config/ConfigModule.java b/caliper/src/main/java/com/google/caliper/config/ConfigModule.java
new file mode 100644
index 0000000..62b64bb
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/config/ConfigModule.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012 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.config;
+
+import dagger.Module;
+import dagger.Provides;
+
+import java.util.logging.LogManager;
+import javax.inject.Singleton;
+
+/**
+ * Bindings for Caliper configuration.
+ */
+@Module
+public final class ConfigModule {
+
+  /**
+   * The {@code doNotRemove} parameter is required here to ensure that the logging configuration
+   * is loaded and used to update the logger settings before the configuration is loaded.
+   */
+  @Provides @Singleton
+  static CaliperConfig provideCaliperConfig(
+      CaliperConfigLoader configLoader,
+      @SuppressWarnings("unused") LoggingConfigLoader doNotRemove)
+      throws InvalidConfigurationException {
+
+    return configLoader.loadOrCreate();
+  }
+
+  @Provides static LogManager provideLogManager() {
+    return LogManager.getLogManager();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/config/InstrumentConfig.java b/caliper/src/main/java/com/google/caliper/config/InstrumentConfig.java
new file mode 100644
index 0000000..5715a16
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/config/InstrumentConfig.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 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.config;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.caliper.model.InstrumentSpec;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Map;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * This is the configuration passed to the instrument by the user. This differs from the
+ * {@link InstrumentSpec} in that any number of configurations can yield the same spec
+ * (due to default option values).
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+@Immutable
+public final class InstrumentConfig {
+  private final String className;
+  private final ImmutableMap<String, String> options;
+
+  private InstrumentConfig(Builder builder) {
+    this.className = builder.className;
+    this.options = builder.optionsBuilder.build();
+  }
+
+  public String className() {
+    return className;
+  }
+
+  public ImmutableMap<String, String> options() {
+    return options;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof InstrumentConfig) {
+      InstrumentConfig that = (InstrumentConfig) obj;
+      return this.className.equals(that.className)
+          && this.options.equals(that.options);
+    } else {
+      return false;
+    }
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(className, options);
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("className", className)
+        .add("options", options)
+        .toString();
+  }
+
+  static final class Builder {
+    private String className;
+    private final ImmutableMap.Builder<String, String> optionsBuilder = ImmutableMap.builder();
+
+    public Builder className(String className) {
+      this.className = checkNotNull(className);
+      return this;
+    }
+
+    public Builder instrumentClass(Class<?> insturmentClass) {
+      return className(insturmentClass.getName());
+    }
+
+    public Builder addOption(String option, String value) {
+      optionsBuilder.put(option, value);
+      return this;
+    }
+
+    public Builder addAllOptions(Map<String, String> options) {
+      optionsBuilder.putAll(options);
+      return this;
+    }
+
+    public InstrumentConfig build() {
+      checkState(className != null);
+      return new InstrumentConfig(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/config/InvalidConfigurationException.java b/caliper/src/main/java/com/google/caliper/config/InvalidConfigurationException.java
new file mode 100644
index 0000000..5d1df97
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/config/InvalidConfigurationException.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 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.config;
+
+import java.io.PrintWriter;
+
+/**
+ * Thrown when an invalid configuration has been specified by the user.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class InvalidConfigurationException extends RuntimeException {
+  public InvalidConfigurationException() {
+    super();
+  }
+
+  public InvalidConfigurationException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public InvalidConfigurationException(String message) {
+    super(message);
+  }
+
+  public InvalidConfigurationException(Throwable cause) {
+    super(cause);
+  }
+
+  public void display(PrintWriter writer) {
+    writer.println(getMessage());
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/config/LoggingConfigLoader.java b/caliper/src/main/java/com/google/caliper/config/LoggingConfigLoader.java
new file mode 100644
index 0000000..4ee216c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/config/LoggingConfigLoader.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 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.config;
+
+import static java.util.logging.Level.SEVERE;
+import static java.util.logging.Level.WARNING;
+
+import com.google.caliper.model.Run;
+import com.google.caliper.options.CaliperDirectory;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.io.Closer;
+
+import org.joda.time.format.ISODateTimeFormat;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.logging.FileHandler;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Loading the logging configuration at {@code ~/.caliper/logging.properties} if present.
+ */
+@Singleton
+final class LoggingConfigLoader {
+  private static final Logger logger = Logger.getLogger(LoggingConfigLoader.class.getName());
+
+  private final File caliperDirectory;
+  private final LogManager logManager;
+  private final Run run;
+
+  @Inject LoggingConfigLoader(@CaliperDirectory File caliperDirectory, LogManager logManager,
+      Run run) {
+    this.caliperDirectory = caliperDirectory;
+    this.logManager = logManager;
+    this.run = run;
+  }
+
+  @Inject void loadLoggingConfig() {
+    File loggingPropertiesFile = new File(caliperDirectory, "logging.properties");
+    if (loggingPropertiesFile.isFile()) {
+      Closer closer = Closer.create();
+      FileInputStream fis = null;
+      try {
+        fis = closer.register(new FileInputStream(loggingPropertiesFile));
+        logManager.readConfiguration(fis);
+      } catch (SecurityException e) {
+        logConfigurationException(e);
+      } catch (IOException e) {
+        logConfigurationException(e);
+      } finally {
+        try {
+          closer.close();
+        } catch (IOException e) {
+          logger.log(SEVERE, "could not close " + loggingPropertiesFile, e);
+        }
+      }
+      logger.info(String.format("Using logging configuration at %s", loggingPropertiesFile));
+    } else {
+      try {
+        maybeLoadDefaultLogConfiguration(LogManager.getLogManager());
+      } catch (SecurityException e) {
+        logConfigurationException(e);
+      } catch (IOException e) {
+        logConfigurationException(e);
+      }
+    }
+  }
+
+  @VisibleForTesting void maybeLoadDefaultLogConfiguration(LogManager logManager)
+      throws SecurityException, IOException {
+    logManager.reset();
+    File logDirectory = new File(caliperDirectory, "log");
+    logDirectory.mkdirs();
+    FileHandler fileHandler = new FileHandler(String.format("%s%c%s.%s.log",
+        logDirectory.getAbsolutePath(), File.separatorChar,
+        ISODateTimeFormat.basicDateTimeNoMillis().print(run.startTime()), run.id()));
+    fileHandler.setEncoding(Charsets.UTF_8.name());
+    fileHandler.setFormatter(new SimpleFormatter());
+    Logger globalLogger = logManager.getLogger("");
+    globalLogger.addHandler(fileHandler);
+  }
+
+  private static void logConfigurationException(Exception e) {
+    logger.log(WARNING, "Could not apply the logging configuration", e);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/config/ResultProcessorConfig.java b/caliper/src/main/java/com/google/caliper/config/ResultProcessorConfig.java
new file mode 100644
index 0000000..cbaa0eb
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/config/ResultProcessorConfig.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 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.config;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Map;
+
+/**
+ * This is the configuration passed to the {@link com.google.caliper.api.ResultProcessor} by the
+ * user.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public class ResultProcessorConfig {
+  private final String className;
+  private final ImmutableMap<String, String> options;
+
+  private ResultProcessorConfig(Builder builder) {
+    this.className = builder.className;
+    this.options = builder.optionsBuilder.build();
+  }
+
+  public String className() {
+    return className;
+  }
+
+  public ImmutableMap<String, String> options() {
+    return options;
+  }
+
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof ResultProcessorConfig) {
+      ResultProcessorConfig that = (ResultProcessorConfig) obj;
+      return this.className.equals(that.className)
+          && this.options.equals(that.options);
+    } else {
+      return false;
+    }
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(className, options);
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("className", className)
+        .add("options", options)
+        .toString();
+  }
+
+  static final class Builder {
+    private String className;
+    private ImmutableMap.Builder<String, String> optionsBuilder = ImmutableMap.builder();
+
+    public Builder className(String className) {
+      this.className = checkNotNull(className);
+      return this;
+    }
+
+    public Builder addOption(String option, String value) {
+      this.optionsBuilder.put(option, value);
+      return this;
+    }
+
+    public Builder addAllOptions(Map<String, String> options) {
+      this.optionsBuilder.putAll(options);
+      return this;
+    }
+
+    public ResultProcessorConfig build() {
+      return new ResultProcessorConfig(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/config/VmConfig.java b/caliper/src/main/java/com/google/caliper/config/VmConfig.java
new file mode 100644
index 0000000..fcb63ca
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/config/VmConfig.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2012 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.config;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.caliper.model.VmSpec;
+import com.google.caliper.platform.Platform;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.File;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * This is the configuration passed to the VM by the user. This differs from the {@link VmSpec}
+ * in that any number of configurations can yield the same spec (due to default flag values) and any
+ * number of specs can come from a single configuration (due to
+ * <a href="http://www.oracle.com/technetwork/java/ergo5-140223.html">ergonomics</a>).
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class VmConfig {
+  private final Platform platform;
+  private final File vmHome;
+  private final ImmutableList<String> options;
+
+  @GuardedBy("this")
+  private File vmExecutable;
+
+  private VmConfig(Builder builder) {
+    this.platform = builder.platform;
+    this.vmHome = builder.vmHome;
+    this.options = builder.optionsBuilder.build();
+  }
+
+  @VisibleForTesting
+  public VmConfig(File vmHome, Iterable<String> options, File vmExecutable, Platform platform) {
+    this.platform = platform;
+    this.vmHome = checkNotNull(vmHome);
+    this.vmExecutable = checkNotNull(vmExecutable);
+    this.options = ImmutableList.copyOf(options);
+  }
+
+  public File vmHome() {
+    return vmHome;
+  }
+
+  public synchronized File vmExecutable() {
+    if (vmExecutable == null) {
+      vmExecutable = platform.vmExecutable(vmHome);
+    }
+    return vmExecutable;
+  }
+
+  public ImmutableList<String> options() {
+    return options;
+  }
+
+  public String platformName() {
+    return platform.name();
+  }
+
+  public String workerClassPath() {
+    return platform.workerClassPath();
+  }
+
+  public ImmutableSet<String> workerProcessArgs() {
+    return platform.workerProcessArgs();
+  }
+
+  public ImmutableSet<String> commonInstrumentVmArgs() {
+    return platform.commonInstrumentVmArgs();
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof VmConfig) {
+      VmConfig that = (VmConfig) obj;
+      return this.platform.equals(that.platform)
+          && this.vmHome.equals(that.vmHome)
+          && this.options.equals(that.options);
+    } else {
+      return false;
+    }
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(platform, vmHome, options);
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("platform", platform)
+        .add("vmHome", vmHome)
+        .add("options", options)
+        .toString();
+  }
+
+  @VisibleForTesting public static final class Builder {
+    private final Platform platform;
+    private final File vmHome;
+    private final ImmutableList.Builder<String> optionsBuilder = ImmutableList.builder();
+
+
+    public Builder(Platform platform, File vmHome) {
+      this.platform = checkNotNull(platform);
+      this.vmHome = checkNotNull(vmHome);
+    }
+
+    public Builder addOption(String option) {
+      optionsBuilder.add(option);
+      return this;
+    }
+
+    public Builder addAllOptions(Iterable<String> options) {
+      optionsBuilder.addAll(options);
+      return this;
+    }
+
+    public VmConfig build() {
+      return new VmConfig(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/json/AnnotationExclusionStrategy.java b/caliper/src/main/java/com/google/caliper/json/AnnotationExclusionStrategy.java
new file mode 100644
index 0000000..82937ba
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/json/AnnotationExclusionStrategy.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 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.json;
+
+import com.google.caliper.model.ExcludeFromJson;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+
+/**
+ * An exclusion strategy that excludes elements annotated with {@link ExcludeFromJson}.
+ */
+final class AnnotationExclusionStrategy implements ExclusionStrategy {
+  @Override public boolean shouldSkipField(FieldAttributes f) {
+    return f.getAnnotation(ExcludeFromJson.class) != null;
+  }
+
+  @Override public boolean shouldSkipClass(Class<?> clazz) {
+    return clazz.getAnnotation(ExcludeFromJson.class) != null;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/json/GsonModule.java b/caliper/src/main/java/com/google/caliper/json/GsonModule.java
new file mode 100644
index 0000000..491dcca
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/json/GsonModule.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 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.json;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.internal.bind.TypeAdapters;
+import dagger.Module;
+import dagger.Provides;
+import dagger.Provides.Type;
+import org.joda.time.Instant;
+import java.util.Set;
+
+/**
+ * Binds a {@link Gson} instance suitable for serializing and deserializing Caliper
+ * {@linkplain com.google.caliper.model model} objects.
+ */
+@Module
+public final class GsonModule {
+
+  @Provides(type = Type.SET)
+  static TypeAdapterFactory provideImmutableListTypeAdapterFactory() {
+    return new ImmutableListTypeAdatperFactory();
+  }
+
+  @Provides(type = Type.SET)
+  static TypeAdapterFactory provideImmutableMapTypeAdapterFactory() {
+    return new ImmutableMapTypeAdapterFactory();
+  }
+
+  @Provides(type = Type.SET)
+  static TypeAdapterFactory provideNaturallySortedMapTypeAdapterFactory() {
+    return new NaturallySortedMapTypeAdapterFactory();
+  }
+
+  @Provides(type = Type.SET)
+  static TypeAdapterFactory provideImmutableMultimapTypeAdapterFactory() {
+    return new ImmutableMultimapTypeAdapterFactory();
+  }
+
+  @Provides
+  static ExclusionStrategy provideAnnotationExclusionStrategy() {
+    return new AnnotationExclusionStrategy();
+  }
+
+  @Provides(type = Type.SET)
+  static TypeAdapterFactory provideTypeAdapterFactoryForInstant(
+      InstantTypeAdapter typeAdapter) {
+    return TypeAdapters.newFactory(Instant.class, typeAdapter);
+  }
+
+  @Provides static InstantTypeAdapter provideInstantTypeAdapter() {
+    return new InstantTypeAdapter();
+  }
+
+  @Provides static Gson provideGson(Set<TypeAdapterFactory> typeAdapterFactories,
+      ExclusionStrategy exclusionStrategy) {
+    GsonBuilder gsonBuilder = new GsonBuilder().setExclusionStrategies(exclusionStrategy);
+    for (TypeAdapterFactory typeAdapterFactory : typeAdapterFactories) {
+      gsonBuilder.registerTypeAdapterFactory(typeAdapterFactory);
+    }
+    return gsonBuilder.create();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/json/ImmutableListTypeAdatperFactory.java b/caliper/src/main/java/com/google/caliper/json/ImmutableListTypeAdatperFactory.java
new file mode 100644
index 0000000..5a7c925
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/json/ImmutableListTypeAdatperFactory.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 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.json;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Serializes and deserializes {@link ImmutableList} instances using an {@link ArrayList} as an
+ * intermediary.
+ */
+final class ImmutableListTypeAdatperFactory implements TypeAdapterFactory {
+  @SuppressWarnings("unchecked")
+  @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+    Type type = typeToken.getType();
+    if (typeToken.getRawType() != ImmutableList.class
+        || !(type instanceof ParameterizedType)) {
+      return null;
+    }
+
+    com.google.common.reflect.TypeToken<ImmutableList<?>> betterToken =
+        (com.google.common.reflect.TypeToken<ImmutableList<?>>)
+            com.google.common.reflect.TypeToken.of(typeToken.getType());
+    final TypeAdapter<ArrayList<?>> arrayListAdapter =
+        (TypeAdapter<ArrayList<?>>) gson.getAdapter(
+            TypeToken.get(betterToken.getSupertype(List.class).getSubtype(ArrayList.class)
+                .getType()));
+    return new TypeAdapter<T>() {
+      @Override public void write(JsonWriter out, T value) throws IOException {
+        ArrayList<?> arrayList = Lists.newArrayList((List<?>) value);
+        arrayListAdapter.write(out, arrayList);
+      }
+
+      @Override public T read(JsonReader in) throws IOException {
+        ArrayList<?> arrayList = arrayListAdapter.read(in);
+        return (T) ImmutableList.copyOf(arrayList);
+      }
+    };
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/json/ImmutableMapTypeAdapterFactory.java b/caliper/src/main/java/com/google/caliper/json/ImmutableMapTypeAdapterFactory.java
new file mode 100644
index 0000000..fec1739
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/json/ImmutableMapTypeAdapterFactory.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 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.json;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Serializes and deserializes {@link ImmutableMap} instances using a {@link HashMap} as an
+ * intermediary.
+ */
+final class ImmutableMapTypeAdapterFactory implements TypeAdapterFactory {
+  @SuppressWarnings("unchecked")
+  @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+    Type type = typeToken.getType();
+    if (typeToken.getRawType() != ImmutableMap.class
+        || !(type instanceof ParameterizedType)) {
+      return null;
+    }
+
+    com.google.common.reflect.TypeToken<ImmutableMap<?, ?>> betterToken =
+        (com.google.common.reflect.TypeToken<ImmutableMap<?, ?>>)
+            com.google.common.reflect.TypeToken.of(typeToken.getType());
+    final TypeAdapter<HashMap<?, ?>> hashMapAdapter =
+        (TypeAdapter<HashMap<?, ?>>) gson.getAdapter(
+            TypeToken.get(betterToken.getSupertype(Map.class).getSubtype(HashMap.class)
+                .getType()));
+    return new TypeAdapter<T>() {
+      @Override public void write(JsonWriter out, T value) throws IOException {
+        HashMap<?, ?> hashMap = Maps.newHashMap((Map<?, ?>) value);
+        hashMapAdapter.write(out, hashMap);
+      }
+
+      @Override public T read(JsonReader in) throws IOException {
+        HashMap<?, ?> hashMap = hashMapAdapter.read(in);
+        return (T) ImmutableMap.copyOf(hashMap);
+      }
+    };
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/json/ImmutableMultimapTypeAdapterFactory.java b/caliper/src/main/java/com/google/caliper/json/ImmutableMultimapTypeAdapterFactory.java
new file mode 100644
index 0000000..9781388
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/json/ImmutableMultimapTypeAdapterFactory.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2012 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.json;
+
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.common.reflect.TypeParameter;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Serializes and deserializes {@link ImmutableMultimap} instances using maps of collections as
+ * intermediaries.
+ */
+final class ImmutableMultimapTypeAdapterFactory implements TypeAdapterFactory {
+  private static <K, V> TypeToken<Map<K, List<V>>> getMapOfListsToken(
+      TypeToken<ListMultimap<K, V>> from) {
+    ParameterizedType rawType = (ParameterizedType) from.getSupertype(ListMultimap.class).getType();
+    @SuppressWarnings("unchecked") // key type is K
+    TypeToken<K> keyType = (TypeToken<K>) TypeToken.of(rawType.getActualTypeArguments()[0]);
+    @SuppressWarnings("unchecked") // value type is V
+    TypeToken<V> valueType = (TypeToken<V>) TypeToken.of(rawType.getActualTypeArguments()[1]);
+    return new TypeToken<Map<K, List<V>>>() {}
+        .where(new TypeParameter<K>() {}, keyType)
+        .where(new TypeParameter<V>() {}, valueType);
+  }
+
+  private static <K, V> TypeToken<Map<K, Set<V>>> getMapOfSetsToken(
+      TypeToken<SetMultimap<K, V>> from) {
+    ParameterizedType rawType = (ParameterizedType) from.getSupertype(SetMultimap.class).getType();
+    @SuppressWarnings("unchecked") // key type is K
+    TypeToken<K> keyType = (TypeToken<K>) TypeToken.of(rawType.getActualTypeArguments()[0]);
+    @SuppressWarnings("unchecked") // value type is V
+    TypeToken<V> valueType = (TypeToken<V>) TypeToken.of(rawType.getActualTypeArguments()[1]);
+    return new TypeToken<Map<K, Set<V>>>() {}
+        .where(new TypeParameter<K>() {}, keyType)
+        .where(new TypeParameter<V>() {}, valueType);
+  }
+
+  @Override
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  public <T> TypeAdapter<T> create(Gson gson, com.google.gson.reflect.TypeToken<T> typeToken) {
+    if (ImmutableListMultimap.class.isAssignableFrom(typeToken.getRawType())) {
+      TypeToken<Map<?, List<?>>> mapToken =
+          getMapOfListsToken((TypeToken) TypeToken.of(typeToken.getType()));
+      final TypeAdapter<Map<?, List<?>>> adapter =
+          (TypeAdapter<Map<?, List<?>>>) gson.getAdapter(
+              com.google.gson.reflect.TypeToken.get(mapToken.getType()));
+      return new TypeAdapter<T>() {
+        @Override public void write(JsonWriter out, T value) throws IOException {
+          ImmutableListMultimap<?, ?> multimap = (ImmutableListMultimap<?, ?>) value;
+          adapter.write(out, (Map) multimap.asMap());
+        }
+
+        @Override public T read(JsonReader in) throws IOException {
+          Map<?, List<?>> value = adapter.read(in);
+          ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder();
+          for (Entry<?, List<?>> entry : value.entrySet()) {
+            builder.putAll(entry.getKey(), entry.getValue());
+          }
+          return (T) builder.build();
+        }
+      };
+    } else if (ImmutableSetMultimap.class.isAssignableFrom(typeToken.getRawType())) {
+      TypeToken<Map<?, Set<?>>> mapToken =
+          getMapOfSetsToken((TypeToken) TypeToken.of(typeToken.getType()));
+      final TypeAdapter<Map<?, Set<?>>> adapter =
+          (TypeAdapter<Map<?, Set<?>>>) gson.getAdapter(
+              com.google.gson.reflect.TypeToken.get(mapToken.getType()));
+      return new TypeAdapter<T>() {
+        @Override public void write(JsonWriter out, T value) throws IOException {
+          ImmutableSetMultimap<?, ?> multimap = (ImmutableSetMultimap<?, ?>) value;
+          adapter.write(out, (Map) multimap.asMap());
+        }
+
+        @Override public T read(JsonReader in) throws IOException {
+          Map<?, Set<?>> value = adapter.read(in);
+          ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder();
+          for (Entry<?, Set<?>> entry : value.entrySet()) {
+            builder.putAll(entry.getKey(), entry.getValue());
+          }
+          return (T) builder.build();
+        }
+      };
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/json/InstantTypeAdapter.java b/caliper/src/main/java/com/google/caliper/json/InstantTypeAdapter.java
new file mode 100644
index 0000000..ae500f7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/json/InstantTypeAdapter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 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.json;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import org.joda.time.Instant;
+import org.joda.time.format.ISODateTimeFormat;
+
+import java.io.IOException;
+
+/**
+ * Serializes and deserializes {@link Instant} instances.
+ */
+final class InstantTypeAdapter extends TypeAdapter<Instant> {
+  @Override public void write(JsonWriter out, Instant value) throws IOException {
+    out.value(ISODateTimeFormat.dateTime().print(value));
+  }
+
+  @Override public Instant read(JsonReader in) throws IOException {
+    return ISODateTimeFormat.dateTime().parseDateTime(in.nextString()).toInstant();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/json/NaturallySortedMapTypeAdapterFactory.java b/caliper/src/main/java/com/google/caliper/json/NaturallySortedMapTypeAdapterFactory.java
new file mode 100644
index 0000000..92fdb19
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/json/NaturallySortedMapTypeAdapterFactory.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012 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.json;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Serializes and deserializes {@link SortedMap} instances using a {@link TreeMap} with natural
+ * ordering as an intermediary.
+ */
+final class NaturallySortedMapTypeAdapterFactory implements TypeAdapterFactory {
+  @SuppressWarnings("rawtypes")
+  private static final ImmutableSet<Class<? extends SortedMap>> CLASSES =
+      ImmutableSet.of(SortedMap.class, TreeMap.class);
+
+  @SuppressWarnings("unchecked")
+  @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+    Type type = typeToken.getType();
+    if (!CLASSES.contains(typeToken.getRawType())
+        || !(type instanceof ParameterizedType)) {
+      return null;
+    }
+
+    com.google.common.reflect.TypeToken<SortedMap<?, ?>> betterToken =
+        (com.google.common.reflect.TypeToken<SortedMap<?, ?>>)
+            com.google.common.reflect.TypeToken.of(typeToken.getType());
+    final TypeAdapter<Map<?, ?>> mapAdapter =
+        (TypeAdapter<Map<?, ?>>) gson.getAdapter(
+            TypeToken.get(betterToken.getSupertype(Map.class).getType()));
+    return new TypeAdapter<T>() {
+      @Override public void write(JsonWriter out, T value) throws IOException {
+        TreeMap<?, ?> treeMap = Maps.newTreeMap((SortedMap<?, ?>) value);
+        mapAdapter.write(out, treeMap);
+      }
+
+      @SuppressWarnings("rawtypes")
+      @Override public T read(JsonReader in) throws IOException {
+        TreeMap treeMap = Maps.newTreeMap();
+        treeMap.putAll(mapAdapter.read(in));
+        return (T) treeMap;
+      }
+    };
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/memory/Chain.java b/caliper/src/main/java/com/google/caliper/memory/Chain.java
new file mode 100644
index 0000000..115141e
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/memory/Chain.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2012 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.memory;
+
+import com.google.common.base.Preconditions;
+
+import java.lang.reflect.Field;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * A chain of references, which starts at a root object and leads to a
+ * particular value (either an object or a primitive).
+ */
+public abstract class Chain {
+  private final Object value;
+  private final Chain parent;
+
+  Chain(Chain parent, Object value) {
+    this.parent = parent;
+    this.value = value;
+  }
+
+  static Chain root(Object value) {
+    return new Chain(null, Preconditions.checkNotNull(value)) {
+      @Override
+      public Class<?> getValueType() {
+        return getValue().getClass();
+      }
+    };
+  }
+
+  FieldChain appendField(Field field, Object value) {
+    return new FieldChain(this, Preconditions.checkNotNull(field), value);
+  }
+
+  ArrayIndexChain appendArrayIndex(int arrayIndex, Object value) {
+    return new ArrayIndexChain(this, arrayIndex, value);
+  }
+
+  /**
+   * Returns whether this chain has a parent. This returns false only when
+   * this chain represents the root object itself.
+   */
+  public boolean hasParent() {
+    return parent != null;
+  }
+
+  /**
+   * Returns the parent chain, from which this chain was created.
+   * @throws IllegalStateException if {@code !hasParent()}, then an
+   */
+  public @Nonnull Chain getParent() {
+    Preconditions.checkState(parent != null, "This is the root value, it has no parent");
+    return parent;
+  }
+
+  /**
+   * Returns the value that this chain leads to. If the value is a primitive,
+   * a wrapper object is returned instead.
+   */
+  public @Nullable Object getValue() {
+    return value;
+  }
+
+  public abstract @Nonnull Class<?> getValueType();
+
+  /**
+   * Returns whether the connection of the parent chain and this chain is
+   * through a field (of the getParent().getValue().getClass() class).
+   */
+  public boolean isThroughField() {
+    return false;
+  }
+
+  /**
+   * Returns whether the connection of the parent chain and this chain is
+   * through an array index, i.e. the parent leads to an array, and this
+   * chain leads to an element of that array.
+   */
+  public boolean isThroughArrayIndex() {
+    return false;
+  }
+
+  /**
+   * Returns whether the value of this chain represents a primitive.
+   */
+  public boolean isPrimitive() {
+    return getValueType().isPrimitive();
+  }
+
+  /**
+   * Returns the root object of this chain.
+   */
+  public @Nonnull Object getRoot() {
+    Chain current = this;
+    while (current.hasParent()) {
+      current = current.getParent();
+    }
+    return current.getValue();
+  }
+
+  Deque<Chain> reverse() {
+    Deque<Chain> reverseChain = new ArrayDeque<Chain>(8);
+    Chain current = this;
+    reverseChain.addFirst(current);
+    while (current.hasParent()) {
+      current = current.getParent();
+      reverseChain.addFirst(current);
+    }
+    return reverseChain;
+  }
+
+  @Override public final String toString() {
+    StringBuilder sb = new StringBuilder(32);
+
+    Iterator<Chain> it = reverse().iterator();
+    sb.append(it.next().getValue());
+    while (it.hasNext()) {
+      sb.append("->");
+      Chain current = it.next();
+      if (current.isThroughField()) {
+        sb.append(((FieldChain)current).getField().getName());
+      } else if (current.isThroughArrayIndex()) {
+        sb.append("[").append(((ArrayIndexChain)current).getArrayIndex()).append("]");
+      }
+    }
+    return sb.toString();
+  }
+
+  static class FieldChain extends Chain {
+    private final Field field;
+
+    FieldChain(Chain parent, Field referringField, Object value) {
+      super(parent, value);
+      this.field = referringField;
+    }
+
+    @Override
+    public boolean isThroughField() {
+      return true;
+    }
+
+    @Override
+    public boolean isThroughArrayIndex() {
+      return false;
+    }
+
+    @Override
+    public Class<?> getValueType() {
+      return field.getType();
+    }
+
+    public Field getField() {
+      return field;
+    }
+  }
+
+  static class ArrayIndexChain extends Chain {
+    private final int index;
+
+    ArrayIndexChain(Chain parent, int index, Object value) {
+      super(parent, value);
+      this.index = index;
+    }
+
+    @Override
+    public boolean isThroughField() {
+      return false;
+    }
+
+    @Override
+    public boolean isThroughArrayIndex() {
+      return true;
+    }
+
+    @Override
+    public Class<?> getValueType() {
+      return getParent().getValue().getClass().getComponentType();
+    }
+
+    public int getArrayIndex() {
+      return index;
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/memory/ObjectExplorer.java b/caliper/src/main/java/com/google/caliper/memory/ObjectExplorer.java
new file mode 100644
index 0000000..2c4e4cc
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/memory/ObjectExplorer.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2012 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.memory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.EnumSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A depth-first object graph explorer. The traversal starts at a root (an
+ * {@code Object}) and explores any other reachable object (recursively) or
+ * primitive value, excluding static fields from the traversal. The traversal
+ * is controlled by a user-supplied {@link ObjectVisitor}, which decides for
+ * each explored path whether to continue exploration of that path, and it can
+ * also return a value at the end of the traversal.
+ */
+public final class ObjectExplorer {
+  private ObjectExplorer() { }
+
+  /**
+   * Explores an object graph (defined by a root object and whatever is
+   * reachable through it, following non-static fields) while using an
+   * {@link ObjectVisitor} to both control the traversal and return a value.
+   *
+   * <p>Equivalent to {@code exploreObject(rootObject, visitor,
+   * EnumSet.noneOf(Feature.class))}.
+   *
+   * @param <T> the type of the value obtained (after the traversal) by the
+   * ObjectVisitor
+   * @param rootObject an object to be recursively explored
+   * @param visitor a visitor that is notified for each explored path and
+   * decides whether to continue exploration of that path, and constructs a
+   * return value at the end of the exploration
+   * @return whatever value is returned by the visitor at the end of the
+   * traversal
+   * @see ObjectVisitor
+   */
+  public static <T> T exploreObject(Object rootObject, ObjectVisitor<T> visitor) {
+    return exploreObject(rootObject, visitor, EnumSet.noneOf(Feature.class));
+  }
+
+  /**
+   * Explores an object graph (defined by a root object and whatever is
+   * reachable through it, following non-static fields) while using an
+   * {@link ObjectVisitor} to both control the traversal and return a value.
+   *
+   * <p>The {@code features} further customizes the exploration behavior.
+   * In particular:
+   * <ul>
+   * <li>If {@link Feature#VISIT_PRIMITIVES} is contained in features,
+   * the visitor will also be notified about exploration of primitive values.
+   * <li>If {@link Feature#VISIT_NULL} is contained in features, the visitor
+   * will also be notified about exploration of {@code null} values.
+   * </ul>
+   * In both cases above, the return value of
+   * {@link ObjectVisitor#visit(Chain)} is ignored, since neither primitive
+   * values or {@code null} can be further explored.
+   *
+   * @param <T> the type of the value obtained (after the traversal) by the
+   * ObjectVisitor
+   * @param rootObject an object to be recursively explored
+   * @param visitor a visitor that is notified for each explored path
+   * and decides whether to continue exploration of that path, and constructs
+   * a return value at the end of the exploration
+   * @param features a set of desired features that the object exploration should have
+   * @return whatever value is returned by the visitor at the end of the traversal
+   * @see ObjectVisitor
+   */
+  public static <T> T exploreObject(Object rootObject,
+      ObjectVisitor<T> visitor, EnumSet<Feature> features) {
+    Deque<Chain> stack = new ArrayDeque<Chain>(32);
+    if (rootObject != null) stack.push(Chain.root(rootObject));
+
+    while (!stack.isEmpty()) {
+      Chain chain = stack.pop();
+      //the only place where the return value of visit() is considered
+      ObjectVisitor.Traversal traversal = visitor.visit(chain);
+      switch (traversal) {
+        case SKIP: continue;
+        case EXPLORE: break;
+        default: throw new AssertionError();
+      }
+
+      //only nonnull values pushed in the stack
+      @Nonnull Object value = chain.getValue();
+      Class<?> valueClass = value.getClass();
+      if (valueClass.isArray()) {
+        boolean isPrimitive = valueClass.getComponentType().isPrimitive();
+        /*
+         * Since we push paths to explore in a stack, we push references found in the array in
+         * reverse order, so when we pop them, they will be in the array's order.
+         */
+        for (int i = Array.getLength(value) - 1; i >= 0; i--) {
+          Object childValue = Array.get(value, i);
+          if (isPrimitive) {
+            if (features.contains(Feature.VISIT_PRIMITIVES)) {
+              visitor.visit(chain.appendArrayIndex(i, childValue));
+            }
+          } else if (childValue == null) {
+            if (features.contains(Feature.VISIT_NULL)) {
+              visitor.visit(chain.appendArrayIndex(i, childValue));
+            }
+          } else {
+            stack.push(chain.appendArrayIndex(i, childValue));
+          }
+        }
+      } else {
+        /*
+         * Reflection usually provides fields in declaration order. As above in arrays, we push
+         * them to the stack in reverse order, so when we pop them, we get them in the original
+         * (declaration) order.
+         */
+        final Field[] fields = getAllFields(value);
+        for (int j = fields.length - 1; j >= 0; j--) {
+          final Field field = fields[j];
+          Object childValue = null;
+          try {
+            childValue = field.get(value);
+          } catch (Exception e) {
+            throw new AssertionError(e);
+          }
+          if (childValue == null) { // handling nulls
+            if (features.contains(Feature.VISIT_NULL)) {
+              visitor.visit(chain.appendField(field, childValue));
+            }
+          } else { // handling primitives or references
+            boolean isPrimitive = field.getType().isPrimitive();
+            Chain extendedChain = chain.appendField(field, childValue);
+            if (isPrimitive) {
+              if (features.contains(Feature.VISIT_PRIMITIVES)) {
+                visitor.visit(extendedChain);
+              }
+            } else {
+              stack.push(extendedChain);
+            }
+          }
+        }
+      }
+    }
+    return visitor.result();
+  }
+
+  /**
+   * A stateful predicate that allows exploring an object (the tail of the chain) only once.
+   */
+  static class AtMostOncePredicate implements Predicate<Chain> {
+    private final Set<Object> seen = Collections.newSetFromMap(
+        new IdentityHashMap<Object, Boolean>());
+
+    @Override public boolean apply(Chain chain) {
+      return seen.add(chain.getValue());
+    }
+  }
+
+  static final Predicate<Chain> notEnumFieldsOrClasses = new Predicate<Chain>() {
+    @Override public boolean apply(Chain chain) {
+      return !(Enum.class.isAssignableFrom(chain.getValueType())
+          || chain.getValue() instanceof Class<?>);
+    }
+  };
+
+  static final Function<Chain, Object> chainToObject =
+      new Function<Chain, Object>() {
+    @Override public Object apply(Chain chain) {
+      return chain.getValue();
+    }
+  };
+
+  /**
+   * A cache of {@code Field}s that are accessible for a given {@code Class<?>}.
+   */
+  private static final ConcurrentHashMap<Class<?>, Field[]> clazzFields =
+      new ConcurrentHashMap<Class<?>, Field[]>();
+
+  private static Field[] getAllFields(Object o) {
+    Class<?> clazz = o.getClass();
+    return getAllFields(clazz);
+  }
+
+  /**
+   * Keep a cache of fields per class of interest.
+   *
+   * @param clazz - the {@code Class<?>} to interrogate.
+   * @return An array of fields of the given class.
+   */
+  private static Field[] getAllFields(Class<?> clazz) {
+    Field[] f = clazzFields.get(clazz);
+    if (f == null) {
+      f = computeAllFields(clazz);
+      Field[] u = clazzFields.putIfAbsent(clazz, f);
+      return u == null ? f : u;
+    }
+    return f;
+  }
+
+  private static Field[] computeAllFields(Class<?> clazz) {
+    List<Field> fields = Lists.newArrayListWithCapacity(8);
+    while (clazz != null) {
+      for (Field field : clazz.getDeclaredFields()) {
+        // add only non-static fields
+        if (!Modifier.isStatic(field.getModifiers())) {
+          fields.add(field);
+        }
+      }
+      clazz = clazz.getSuperclass();
+    }
+
+    // all together so there is only one security check
+    Field[] afields = fields.toArray(new Field[fields.size()]);
+    AccessibleObject.setAccessible(afields, true);
+    return afields;
+  }
+
+  /**
+   * Enumeration of features that may be optionally requested for an object
+   * traversal.
+   *
+   * @see ObjectExplorer#exploreObject(Object, ObjectVisitor, EnumSet)
+   */
+  public enum Feature {
+    /**
+     * Null references should be visited.
+     */
+    VISIT_NULL,
+
+    /**
+     * Primitive values should be visited.
+     */
+    VISIT_PRIMITIVES
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/memory/ObjectGraphMeasurer.java b/caliper/src/main/java/com/google/caliper/memory/ObjectGraphMeasurer.java
new file mode 100644
index 0000000..3cacaeb
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/memory/ObjectGraphMeasurer.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2012 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.memory;
+
+import com.google.caliper.memory.ObjectExplorer.Feature;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multiset;
+
+import java.util.EnumSet;
+
+/**
+ * A tool that can qualitatively measure the footprint
+ * ({@literal e.g.}, number of objects, references,
+ * primitives) of a graph structure.
+ */
+public final class ObjectGraphMeasurer {
+  /**
+   * The footprint of an object graph.
+   */
+  public final static class Footprint {
+    private final int objects;
+    private final int nonNullRefs;
+    private final int nullRefs;
+    private final ImmutableMultiset<Class<?>> primitives;
+
+    private static final ImmutableSet<Class<?>> primitiveTypes = ImmutableSet.<Class<?>>of(
+        boolean.class, byte.class, char.class, short.class,
+        int.class, float.class, long.class, double.class);
+
+    /**
+     * Constructs a Footprint, by specifying the number of objects,
+     * references, and primitives (represented as a {@link Multiset}).
+     *
+     * @param objects the number of objects
+     * @param nonNullRefs the number of non-null references
+     * @param nullRefs the number of null references
+     * @param primitives the number of primitives (represented by the
+     * respective primitive classes, e.g. {@code int.class} etc)
+     */
+    public Footprint(int objects, int nonNullRefs, int nullRefs,
+        Multiset<Class<?>> primitives) {
+      Preconditions.checkArgument(objects >= 0, "Negative number of objects");
+      Preconditions.checkArgument(nonNullRefs >= 0, "Negative number of references");
+      Preconditions.checkArgument(nullRefs >= 0, "Negative number of references");
+      Preconditions.checkArgument(primitiveTypes.containsAll(primitives.elementSet()),
+          "Unexpected primitive type");
+      this.objects = objects;
+      this.nonNullRefs = nonNullRefs;
+      this.nullRefs = nullRefs;
+      this.primitives = ImmutableMultiset.copyOf(primitives);
+    }
+
+    /**
+     * Returns the number of objects of this footprint.
+     */
+    public int getObjects() {
+      return objects;
+    }
+
+    /**
+     * Returns the number of non-null references of this footprint.
+     */
+    public int getNonNullReferences() {
+      return nonNullRefs;
+    }
+
+    /**
+     * Returns the number of null references of this footprint.
+     */
+    public int getNullReferences() {
+      return nullRefs;
+    }
+
+    /**
+     * Returns the number of all references (null and non-null) of this footprint.
+     */
+    public int getAllReferences() {
+      return nonNullRefs + nullRefs;
+    }
+
+    /**
+     * Returns the number of primitives of this footprint
+     * (represented by the respective primitive classes,
+     * {@literal e.g.} {@code int.class} etc).
+     */
+    public ImmutableMultiset<Class<?>> getPrimitives() {
+      return primitives;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(getClass().getName(),
+          objects, nonNullRefs, nullRefs, primitives);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof Footprint) {
+        Footprint that = (Footprint) o;
+        return this.objects == that.objects
+            && this.nonNullRefs == that.nonNullRefs
+            && this.nullRefs == that.nullRefs
+            && this.primitives.equals(that.primitives);
+      }
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("Objects", objects)
+          .add("NonNullRefs", nonNullRefs)
+          .add("NullRefs", nullRefs)
+          .add("Primitives", primitives)
+          .toString();
+    }
+  }
+
+  /**
+   * Measures the footprint of the specified object graph.
+   * The object graph is defined by a root object and whatever object can be
+   * reached through that, excluding static fields, {@code Class} objects,
+   * and fields defined in {@code enum}s (all these are considered shared
+   * values, which should not contribute to the cost of any single object
+   * graph).
+   *
+   * <p>Equivalent to {@code measure(rootObject, Predicates.alwaysTrue())}.
+   *
+   * @param rootObject the root object of the object graph
+   * @return the footprint of the object graph
+   */
+  public static Footprint measure(Object rootObject) {
+    return measure(rootObject, Predicates.alwaysTrue());
+  }
+
+  /**
+   * Measures the footprint of the specified object graph.
+   * The object graph is defined by a root object and whatever object can be
+   * reached through that, excluding static fields, {@code Class} objects,
+   * and fields defined in {@code enum}s (all these are considered shared
+   * values, which should not contribute to the cost of any single object
+   * graph), and any object for which the user-provided predicate returns
+   * {@code false}.
+   *
+   * @param rootObject the root object of the object graph
+   * @param objectAcceptor a predicate that returns {@code true} for objects
+   * to be explored (and treated as part of the footprint), or {@code false}
+   * to forbid the traversal to traverse the given object
+   * @return the footprint of the object graph
+   */
+  public static Footprint measure(Object rootObject, Predicate<Object> objectAcceptor) {
+    Preconditions.checkNotNull(objectAcceptor, "predicate");
+
+    Predicate<Chain> completePredicate = Predicates.and(ImmutableList.of(
+        ObjectExplorer.notEnumFieldsOrClasses,
+        new ObjectExplorer.AtMostOncePredicate(),
+        Predicates.compose(objectAcceptor, ObjectExplorer.chainToObject)
+    ));
+
+    return ObjectExplorer.exploreObject(rootObject, new ObjectGraphVisitor(completePredicate),
+        EnumSet.of(Feature.VISIT_PRIMITIVES, Feature.VISIT_NULL));
+  }
+
+  private static class ObjectGraphVisitor implements ObjectVisitor<Footprint> {
+    private int objects;
+    // -1 to account for the root, which has no reference leading to it
+    private int nonNullReferences = -1;
+    private int nullReferences = 0;
+    private final Multiset<Class<?>> primitives = HashMultiset.create();
+    private final Predicate<Chain> predicate;
+
+    ObjectGraphVisitor(Predicate<Chain> predicate) {
+      this.predicate = predicate;
+    }
+
+    @Override public Traversal visit(Chain chain) {
+      if (chain.isPrimitive()) {
+        primitives.add(chain.getValueType());
+        return Traversal.SKIP;
+      } else {
+        if (chain.getValue() == null) {
+          nullReferences++;
+        } else {
+          nonNullReferences++;
+        }
+      }
+      if (predicate.apply(chain) && chain.getValue() != null) {
+        objects++;
+        return Traversal.EXPLORE;
+      }
+      return Traversal.SKIP;
+    }
+
+    @Override public Footprint result() {
+      return new Footprint(objects, nonNullReferences, nullReferences,
+          ImmutableMultiset.copyOf(primitives));
+    }
+  }
+
+  private ObjectGraphMeasurer() {}
+}
diff --git a/caliper/src/main/java/com/google/caliper/memory/ObjectVisitor.java b/caliper/src/main/java/com/google/caliper/memory/ObjectVisitor.java
new file mode 100644
index 0000000..2a1320e
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/memory/ObjectVisitor.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 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.memory;
+
+/**
+ * A visitor that controls an object traversal. Implementations
+ * of this interface are passed to {@link ObjectExplorer} exploration methods.
+ *
+ * @param <T> the type of the result that this visitor returns
+ * (can be defined as {@code Void} to denote no result}.
+ *
+ * @see ObjectExplorer
+ */
+public interface ObjectVisitor<T> {
+  /**
+   * Visits an explored value (the whole chain from the root object
+   * leading to the value is provided), and decides whether to continue
+   * the exploration of that value.
+   *
+   * <p>In case the explored value is either primitive or {@code null}
+   * (e.g., if {@code chain.isPrimitive() || chain.getValue() == null}),
+   * the return value is meaningless and is ignored.
+   *
+   * @param chain the chain that leads to the explored value.
+   * @return {@link Traversal#EXPLORE} to denote that the visited object
+   * should be further explored, or {@link Traversal#SKIP} to avoid
+   * exploring it.
+   */
+  Traversal visit(Chain chain);
+
+  /**
+   * Returns an arbitrary value (presumably constructed during the object
+   * graph traversal).
+   */
+  T result();
+
+  /**
+   * Constants that denote how the traversal of a given object (chain)
+   * should continue.
+   */
+  enum Traversal {
+    /**
+     * The visited object should be further explored.
+     */
+    EXPLORE,
+
+    /**
+     * The visited object should not be explored.
+     */
+    SKIP
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/AllocationMeasurement.java b/caliper/src/main/java/com/google/caliper/model/AllocationMeasurement.java
new file mode 100644
index 0000000..5ceed02
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/AllocationMeasurement.java
@@ -0,0 +1,31 @@
+/*
+ * 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.model;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that identifies a given method as an "allocation measurement" method. The method
+ * should take a single int parameter; its return value may be any type. Methods with this
+ * annotation may be used with the {@link com.google.caliper.runner.AllocationInstrument}.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AllocationMeasurement {}
diff --git a/caliper/src/main/java/com/google/caliper/model/ArbitraryMeasurement.java b/caliper/src/main/java/com/google/caliper/model/ArbitraryMeasurement.java
new file mode 100644
index 0000000..bcfdd49
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/ArbitraryMeasurement.java
@@ -0,0 +1,41 @@
+/*
+ * 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.model;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that identifies a given method as an "arbitrary measurement" method. The method should
+ * take no parameters and return a double, which is the measured value.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ArbitraryMeasurement {
+
+  /**
+   * The units for the value returned by this measurement method.
+   */
+  String units() default "";
+
+  /**
+   * Text description of the quantity measured by this measurement method.
+   */
+  String description() default "";
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/BenchmarkSpec.java b/caliper/src/main/java/com/google/caliper/model/BenchmarkSpec.java
new file mode 100644
index 0000000..c2fff4c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/BenchmarkSpec.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import static com.google.caliper.model.PersistentHashing.getPersistentHashFunction;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Maps;
+import com.google.common.hash.Funnel;
+import com.google.common.hash.PrimitiveSink;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.SortedMap;
+
+/**
+ * A specification by which a benchmark method invocation can be uniquely identified.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class BenchmarkSpec implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  static final BenchmarkSpec DEFAULT = new BenchmarkSpec();
+
+  @ExcludeFromJson private int id;
+  private String className;
+  private String methodName;
+  private SortedMap<String, String> parameters;
+  @ExcludeFromJson private int hash;
+
+  private BenchmarkSpec() {
+    this.className = "";
+    this.methodName = "";
+    this.parameters = Maps.newTreeMap();
+  }
+
+  private BenchmarkSpec(Builder builder) {
+    this.className = builder.className;
+    this.methodName = builder.methodName;
+    this.parameters = Maps.newTreeMap(builder.parameters);
+  }
+
+  public String className() {
+    return className;
+  }
+
+  public String methodName() {
+    return methodName;
+  }
+
+  public ImmutableSortedMap<String, String> parameters() {
+    return ImmutableSortedMap.copyOf(parameters);
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof BenchmarkSpec) {
+      BenchmarkSpec that = (BenchmarkSpec) obj;
+      return this.className.equals(that.className)
+          && this.methodName.equals(that.methodName)
+          && this.parameters.equals(that.parameters);
+    } else {
+      return false;
+    }
+  }
+
+  private void initHash() {
+    if (hash == 0) {
+      this.hash = getPersistentHashFunction()
+          .hashObject(this, BenchmarkSpecFunnel.INSTANCE).asInt();
+    }
+  }
+
+  @Override public int hashCode() {
+    initHash();
+    return hash;
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("className", className)
+        .add("methodName", methodName)
+        .add("parameters", parameters)
+        .toString();
+  }
+
+  enum BenchmarkSpecFunnel implements Funnel<BenchmarkSpec> {
+    INSTANCE;
+
+    @Override public void funnel(BenchmarkSpec from, PrimitiveSink into) {
+      into.putUnencodedChars(from.className)
+          .putUnencodedChars(from.methodName);
+      StringMapFunnel.INSTANCE.funnel(from.parameters, into);
+    }
+  }
+
+  public static final class Builder {
+    private String className;
+    private String methodName;
+    private final SortedMap<String, String> parameters = Maps.newTreeMap();
+
+    public Builder className(String className) {
+      this.className = checkNotNull(className);
+      return this;
+    }
+
+    public Builder methodName(String methodName) {
+      this.methodName = checkNotNull(methodName);
+      return this;
+    }
+
+    public Builder addParameter(String parameterName, String value) {
+      this.parameters.put(checkNotNull(parameterName), checkNotNull(value));
+      return this;
+    }
+
+    public Builder addAllParameters(Map<String, String> parameters) {
+      this.parameters.putAll(parameters);
+      return this;
+    }
+
+    public BenchmarkSpec build() {
+      checkState(className != null);
+      checkState(methodName != null);
+      return new BenchmarkSpec(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/Defaults.java b/caliper/src/main/java/com/google/caliper/model/Defaults.java
new file mode 100644
index 0000000..1765e09
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/Defaults.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import org.joda.time.Instant;
+
+import java.util.UUID;
+
+/**
+ * Default instances of non-model classes used by the Caliper model. These are present to facilitate
+ * Gson deserialization.
+ */
+final class Defaults {
+  static final UUID UUID = new UUID(0L, 0L);
+  static final Instant INSTANT = new Instant(0L);
+
+  private Defaults() {}
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/ExcludeFromJson.java b/caliper/src/main/java/com/google/caliper/model/ExcludeFromJson.java
new file mode 100644
index 0000000..fdf0b95
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/ExcludeFromJson.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a field or type should not be serialized to JSON.
+ */
+@Target({FIELD, TYPE})
+@Retention(RUNTIME)
+public @interface ExcludeFromJson {}
diff --git a/caliper/src/main/java/com/google/caliper/model/Host.java b/caliper/src/main/java/com/google/caliper/model/Host.java
new file mode 100644
index 0000000..ecd098c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/Host.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import static com.google.caliper.model.PersistentHashing.getPersistentHashFunction;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Maps;
+import com.google.common.hash.Funnel;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.PrimitiveSink;
+
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.logging.Logger;
+
+/**
+ * The performance-informing properties of the host on which a benchmark is run.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class Host {
+  static final Host DEFAULT = new Host();
+  private static final Logger logger = Logger.getLogger(Host.class.getName());
+
+  @ExcludeFromJson private int id;
+  private SortedMap<String, String> properties;
+  @ExcludeFromJson private int hash;
+
+  private Host() {
+    this.properties = Maps.newTreeMap();
+  }
+
+  private Host(Builder builder) {
+    this.properties = Maps.newTreeMap(builder.properties);
+    // eagerly initialize hash to allow for the test-only hash function
+    initHash(builder.hashFunction);
+  }
+
+  public ImmutableSortedMap<String, String> properties() {
+    return ImmutableSortedMap.copyOf(properties);
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof Host) {
+      Host that = (Host) obj;
+      return this.properties.equals(that.properties);
+    } else {
+      return false;
+    }
+  }
+
+  private void initHash(HashFunction hashFunction) {
+    if (hash == 0) {
+      this.hash = hashFunction.hashObject(this, HostFunnel.INSTANCE).asInt();
+    }
+  }
+
+  private void initHash() {
+    initHash(getPersistentHashFunction());
+  }
+
+  @Override public int hashCode() {
+    initHash();
+    return hash;
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("properties", properties)
+        .toString();
+  }
+
+  enum HostFunnel implements Funnel<Host> {
+    INSTANCE;
+
+    @Override public void funnel(Host from, PrimitiveSink into) {
+      StringMapFunnel.INSTANCE.funnel(from.properties, into);
+    }
+  }
+
+  public static final class Builder {
+    private final SortedMap<String, String> properties = Maps.newTreeMap();
+    private HashFunction hashFunction = getPersistentHashFunction();
+
+    public Builder addProperty(String key, String value) {
+      properties.put(key, value);
+      return this;
+    }
+
+    public Builder addAllProperies(Map<String, String> properties) {
+      this.properties.putAll(properties);
+      return this;
+    }
+
+    /**
+     * This only exists for tests to induce hash collisions. Only use this in test code as changing
+     * the hash function will break persisted objects.
+     */
+    @VisibleForTesting public Builder hashFunctionForTesting(HashFunction hashFunction) {
+      logger.warning("somebody is setting the hash function. this should only be used in tests");
+      this.hashFunction = checkNotNull(hashFunction);
+      return this;
+    }
+
+    public Host build() {
+      return new Host(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/InstrumentSpec.java b/caliper/src/main/java/com/google/caliper/model/InstrumentSpec.java
new file mode 100644
index 0000000..6a7cd77
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/InstrumentSpec.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import static com.google.caliper.model.PersistentHashing.getPersistentHashFunction;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Maps;
+
+import java.util.Map;
+import java.util.SortedMap;
+
+/**
+ * A specification by which the application of an instrument can be uniquely identified.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class InstrumentSpec {
+  static final InstrumentSpec DEFAULT = new InstrumentSpec();
+
+  @ExcludeFromJson
+  private int id;
+  private String className;
+  private SortedMap<String, String> options;
+  @ExcludeFromJson
+  private int hash;
+
+  private InstrumentSpec() {
+    this.className = "";
+    this.options = Maps.newTreeMap();
+  }
+
+  private InstrumentSpec(Builder builder) {
+    this.className = builder.className;
+    this.options = Maps.newTreeMap(builder.options);
+  }
+
+  public String className() {
+    return className;
+  }
+
+  public ImmutableSortedMap<String, String> options() {
+    return ImmutableSortedMap.copyOf(options);
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof InstrumentSpec) {
+      InstrumentSpec that = (InstrumentSpec) obj;
+      return this.className.equals(that.className)
+          && this.options.equals(that.options);
+    } else {
+      return false;
+    }
+  }
+
+  private void initHash() {
+    if (hash == 0) {
+      this.hash = getPersistentHashFunction()
+          .newHasher()
+          .putUnencodedChars(className)
+          .putObject(options, StringMapFunnel.INSTANCE)
+          .hash().asInt();
+    }
+  }
+
+  @Override public int hashCode() {
+    initHash();
+    return hash;
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("className", className)
+        .add("options", options)
+        .toString();
+  }
+
+  public static final class Builder {
+    private String className;
+    private final SortedMap<String, String> options = Maps.newTreeMap();
+
+    public Builder className(String className) {
+      this.className = checkNotNull(className);
+      return this;
+    }
+
+    public Builder instrumentClass(Class<?> insturmentClass) {
+      return className(insturmentClass.getName());
+    }
+
+    public Builder addOption(String option, String value) {
+      this.options.put(option, value);
+      return this;
+    }
+
+    public Builder addAllOptions(Map<String, String> options) {
+      this.options.putAll(options);
+      return this;
+    }
+
+    public InstrumentSpec build() {
+      checkState(className != null);
+      return new InstrumentSpec(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/Measurement.java b/caliper/src/main/java/com/google/caliper/model/Measurement.java
new file mode 100644
index 0000000..8effce7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/Measurement.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Function;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Multimaps;
+
+import java.io.Serializable;
+
+/**
+ * A single, weighted measurement.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public class Measurement implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  public static ImmutableListMultimap<String, Measurement> indexByDescription(
+      Iterable<Measurement> measurements) {
+    return Multimaps.index(measurements, new Function<Measurement, String>() {
+      @Override public String apply(Measurement input) {
+        return input.description;
+      }
+    });
+  }
+
+  @ExcludeFromJson
+  private int id;
+  private Value value;
+  private double weight;
+  private String description;
+
+  private Measurement() {
+    this.value = Value.DEFAULT;
+    this.weight = 0.0;
+    this.description = "";
+  }
+
+  private Measurement(Builder builder) {
+    this.value = builder.value;
+    this.description = builder.description;
+    this.weight = builder.weight;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof Measurement) {
+      Measurement that = (Measurement) obj;
+      return this.value.equals(that.value)
+          && this.weight == that.weight
+          && this.description.equals(that.description);
+    } else {
+      return false;
+    }
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(value, weight, description);
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("value", value)
+        .add("weight", weight)
+        .add("description", description)
+        .toString();
+  }
+
+  public Value value() {
+    return value;
+  }
+
+  public double weight() {
+    return weight;
+  }
+
+  public String description() {
+    return description;
+  }
+
+  public static final class Builder {
+    private Value value;
+    private Double weight;
+    private String description;
+
+    public Builder value(Value value) {
+      this.value = checkNotNull(value);
+      return this;
+    }
+
+    public Builder weight(double weight) {
+      checkArgument(weight > 0);
+      this.weight = weight;
+      return this;
+    }
+
+    public Builder description(String description) {
+      this.description = checkNotNull(description);
+      return this;
+    }
+
+    public Measurement build() {
+      checkArgument(value != null);
+      checkArgument(weight != null);
+      checkArgument(description != null);
+      return new Measurement(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/PersistentHashing.java b/caliper/src/main/java/com/google/caliper/model/PersistentHashing.java
new file mode 100644
index 0000000..48a3e08
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/PersistentHashing.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+
+/**
+ * A utility class for standardizing the hash function that we're using for persistence.
+ */
+final class PersistentHashing {
+  private PersistentHashing() {}
+
+  static HashFunction getPersistentHashFunction() {
+    return Hashing.murmur3_32();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/Run.java b/caliper/src/main/java/com/google/caliper/model/Run.java
new file mode 100644
index 0000000..eb9733b
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/Run.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+import org.joda.time.Instant;
+
+import java.util.UUID;
+
+/**
+ * A single invocation of caliper.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class Run {
+  static final Run DEFAULT = new Run();
+
+  private UUID id;
+  private String label;
+  private Instant startTime;
+
+  private Run() {
+    this.id = Defaults.UUID;
+    this.label = "";
+    this.startTime = Defaults.INSTANT;
+  }
+
+  private Run(Builder builder) {
+    this.id = builder.id;
+    this.label = builder.label;
+    this.startTime = builder.startTime;
+  }
+
+  public UUID id() {
+    return id;
+  }
+
+  public String label() {
+    return label;
+  }
+
+  public Instant startTime() {
+    return startTime;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof Run) {
+      Run that = (Run) obj;
+      return this.id.equals(that.id)
+          && this.label.equals(that.label)
+          && this.startTime.equals(that.startTime);
+    } else {
+      return false;
+    }
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(id, label, startTime);
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("id", id)
+        .add("label", label)
+        .add("startTime", startTime)
+        .toString();
+  }
+
+  public static final class Builder {
+    private UUID id;
+    private String label = "";
+    private Instant startTime;
+
+    public Builder(UUID id) {
+      this.id = checkNotNull(id);
+    }
+
+    public Builder label(String label) {
+      this.label = checkNotNull(label);
+      return this;
+    }
+
+    public Builder startTime(Instant startTime) {
+      this.startTime = checkNotNull(startTime);
+      return this;
+    }
+
+    public Run build() {
+      checkState(id != null);
+      checkState(startTime != null);
+      return new Run(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/Scenario.java b/caliper/src/main/java/com/google/caliper/model/Scenario.java
new file mode 100644
index 0000000..414d4ab
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/Scenario.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import static com.google.caliper.model.PersistentHashing.getPersistentHashFunction;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.caliper.model.BenchmarkSpec.BenchmarkSpecFunnel;
+import com.google.caliper.model.Host.HostFunnel;
+import com.google.caliper.model.VmSpec.VmSpecFunnel;
+import com.google.common.base.MoreObjects;
+
+/**
+ * The combination of properties whose combination, when measured with a particular instrument,
+ * should produce a repeatable result
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class Scenario {
+  static final Scenario DEFAULT = new Scenario();
+
+  @ExcludeFromJson private int id;
+  private Host host;
+  private VmSpec vmSpec;
+  private BenchmarkSpec benchmarkSpec;
+  // TODO(gak): include data about caliper itself and the code being benchmarked
+  @ExcludeFromJson private int hash;
+
+  private Scenario() {
+    this.host = Host.DEFAULT;
+    this.vmSpec = VmSpec.DEFAULT;
+    this.benchmarkSpec = BenchmarkSpec.DEFAULT;
+  }
+
+  private Scenario(Builder builder) {
+    this.host = builder.host;
+    this.vmSpec = builder.vmSpec;
+    this.benchmarkSpec = builder.benchmarkSpec;
+  }
+
+  public Host host() {
+    return host;
+  }
+
+  public VmSpec vmSpec() {
+    return vmSpec;
+  }
+
+  public BenchmarkSpec benchmarkSpec() {
+    return benchmarkSpec;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof Scenario) {
+      Scenario that = (Scenario) obj;
+      return this.host.equals(that.host)
+          && this.vmSpec.equals(that.vmSpec)
+          && this.benchmarkSpec.equals(that.benchmarkSpec);
+    } else {
+      return false;
+    }
+  }
+
+  private void initHash() {
+    if (hash == 0) {
+      this.hash = getPersistentHashFunction()
+          .newHasher()
+          .putObject(host, HostFunnel.INSTANCE)
+          .putObject(vmSpec, VmSpecFunnel.INSTANCE)
+          .putObject(benchmarkSpec, BenchmarkSpecFunnel.INSTANCE)
+          .hash().asInt();
+    }
+  }
+
+  @Override public int hashCode() {
+    initHash();
+    return hash;
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("environment", host)
+        .add("vmSpec", vmSpec)
+        .add("benchmarkSpec", benchmarkSpec)
+        .toString();
+  }
+
+  public static final class Builder {
+    private Host host;
+    private VmSpec vmSpec;
+    private BenchmarkSpec benchmarkSpec;
+
+    public Builder host(Host.Builder hostBuilder) {
+      return host(hostBuilder.build());
+    }
+
+    public Builder host(Host host) {
+      this.host = checkNotNull(host);
+      return this;
+    }
+
+    public Builder vmSpec(VmSpec.Builder vmSpecBuilder) {
+      return vmSpec(vmSpecBuilder.build());
+    }
+
+    public Builder vmSpec(VmSpec vmSpec) {
+      this.vmSpec = checkNotNull(vmSpec);
+      return this;
+    }
+
+    public Builder benchmarkSpec(BenchmarkSpec.Builder benchmarkSpecBuilder) {
+      return benchmarkSpec(benchmarkSpecBuilder.build());
+    }
+
+    public Builder benchmarkSpec(BenchmarkSpec benchmarkSpec) {
+      this.benchmarkSpec = checkNotNull(benchmarkSpec);
+      return this;
+    }
+
+    public Scenario build() {
+      checkState(host != null);
+      checkState(vmSpec != null);
+      checkState(benchmarkSpec != null);
+      return new Scenario(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/StringMapFunnel.java b/caliper/src/main/java/com/google/caliper/model/StringMapFunnel.java
new file mode 100644
index 0000000..ae3f43b
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/StringMapFunnel.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import com.google.common.hash.Funnel;
+import com.google.common.hash.PrimitiveSink;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A simple funnel that inserts string map entries into a funnel in iteration order.
+ */
+enum StringMapFunnel implements Funnel<Map<String, String>> {
+  INSTANCE;
+
+  @Override
+  public void funnel(Map<String, String> from, PrimitiveSink into) {
+    for (Entry<String, String> entry : from.entrySet()) {
+      into.putUnencodedChars(entry.getKey())
+          .putByte((byte) -1) // separate key and value
+          .putUnencodedChars(entry.getValue());
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/Trial.java b/caliper/src/main/java/com/google/caliper/model/Trial.java
new file mode 100644
index 0000000..08334cf
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/Trial.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * An invocation of a single scenario measured with a single instrument and the results thereof.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class Trial { // used to be Result
+  public static final Trial DEFAULT = new Trial();
+
+  private UUID id;
+  private Run run;
+  private InstrumentSpec instrumentSpec;
+  private Scenario scenario;
+  private List<Measurement> measurements;
+
+  private Trial() {
+    this.id = Defaults.UUID;
+    this.run = Run.DEFAULT;
+    this.instrumentSpec = InstrumentSpec.DEFAULT;
+    this.scenario = Scenario.DEFAULT;
+    this.measurements = Lists.newArrayList();
+  }
+
+  private Trial(Builder builder) {
+    this.id = builder.id;
+    this.run = builder.run;
+    this.instrumentSpec = builder.instrumentSpec;
+    this.scenario = builder.scenario;
+    this.measurements = Lists.newArrayList(builder.measurements);
+  }
+
+  public UUID id() {
+    return id;
+  }
+
+  public Run run() {
+    return run;
+  }
+
+  public InstrumentSpec instrumentSpec() {
+    return instrumentSpec;
+  }
+
+  public Scenario scenario() {
+    return scenario;
+  }
+
+  public ImmutableList<Measurement> measurements() {
+    return ImmutableList.copyOf(measurements);
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof Trial) {
+      Trial that = (Trial) obj;
+      return this.id.equals(that.id)
+          && this.run.equals(that.run)
+          && this.instrumentSpec.equals(that.instrumentSpec)
+          && this.scenario.equals(that.scenario)
+          && this.measurements.equals(that.measurements);
+    } else {
+      return false;
+    }
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(id, run, instrumentSpec, scenario, measurements);
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("id", id)
+        .add("run", run)
+        .add("instrumentSpec", instrumentSpec)
+        .add("scenario", scenario)
+        .add("measurements", measurements)
+        .toString();
+  }
+
+  public static final class Builder {
+    private final UUID id;
+    private Run run;
+    private InstrumentSpec instrumentSpec;
+    private Scenario scenario;
+    private final List<Measurement> measurements = Lists.newArrayList();
+
+    public Builder(UUID id) {
+      this.id = checkNotNull(id);
+    }
+
+    public Builder run(Run.Builder runBuilder) {
+      return run(runBuilder.build());
+    }
+
+    public Builder run(Run run) {
+      this.run = checkNotNull(run);
+      return this;
+    }
+
+    public Builder instrumentSpec(InstrumentSpec.Builder instrumentSpecBuilder) {
+      return instrumentSpec(instrumentSpecBuilder.build());
+    }
+
+    public Builder instrumentSpec(InstrumentSpec instrumentSpec) {
+      this.instrumentSpec = checkNotNull(instrumentSpec);
+      return this;
+    }
+
+    public Builder scenario(Scenario.Builder scenarioBuilder) {
+      return scenario(scenarioBuilder.build());
+    }
+
+    public Builder scenario(Scenario scenario) {
+      this.scenario = checkNotNull(scenario);
+      return this;
+    }
+
+    public Builder addMeasurement(Measurement.Builder measurementBuilder) {
+      return addMeasurement(measurementBuilder.build());
+    }
+
+    public Builder addMeasurement(Measurement measurement) {
+      this.measurements.add(measurement);
+      return this;
+    }
+
+    public Builder addAllMeasurements(Iterable<Measurement> measurements) {
+      Iterables.addAll(this.measurements, measurements);
+      return this;
+    }
+
+    public Trial build() {
+      checkState(run != null);
+      checkState(instrumentSpec != null);
+      checkState(scenario != null);
+      return new Trial(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/Value.java b/caliper/src/main/java/com/google/caliper/model/Value.java
new file mode 100644
index 0000000..b0daee9
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/Value.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Objects;
+
+import java.io.Serializable;
+
+/**
+ * A magnitude with units.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public class Value implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  static final Value DEFAULT = new Value();
+
+  public static Value create(double value, String unit) {
+    return new Value(value, checkNotNull(unit));
+  }
+
+  private double magnitude;
+  // TODO(gak): do something smarter than string for units
+  // TODO(gak): give guidelines for how to specify units.  E.g. s or seconds
+  private String unit;
+
+  private Value() {
+    this.magnitude = 0.0;
+    this.unit = "";
+  }
+
+  private Value(double value, String unit) {
+    this.magnitude = value;
+    this.unit = unit;
+  }
+
+  public String unit() {
+    return unit;
+  }
+
+  public double magnitude() {
+    return magnitude;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof Value) {
+      Value that = (Value) obj;
+      return this.magnitude == that.magnitude
+          && this.unit.equals(that.unit);
+    } else {
+      return false;
+    }
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(magnitude, unit);
+  }
+
+  @Override public String toString() {
+    return new StringBuilder().append(magnitude).append(unit).toString();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/VmSpec.java b/caliper/src/main/java/com/google/caliper/model/VmSpec.java
new file mode 100644
index 0000000..d4b835b
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/VmSpec.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import static com.google.caliper.model.PersistentHashing.getPersistentHashFunction;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Maps;
+import com.google.common.hash.Funnel;
+import com.google.common.hash.PrimitiveSink;
+
+import java.util.Map;
+import java.util.SortedMap;
+
+/**
+ * A configuration of a virtual machine.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class VmSpec {
+  static final VmSpec DEFAULT = new VmSpec();
+
+  @ExcludeFromJson private int id;
+  private SortedMap<String, String> properties;
+  private SortedMap<String, String> options;
+  @ExcludeFromJson private int hash;
+
+  private VmSpec() {
+    this.properties = Maps.newTreeMap();
+    this.options = Maps.newTreeMap();
+  }
+
+  private VmSpec(Builder builder) {
+    this.properties = Maps.newTreeMap(builder.properties);
+    this.options = Maps.newTreeMap(builder.options);
+  }
+
+  public ImmutableSortedMap<String, String> options() {
+    return ImmutableSortedMap.copyOf(options);
+  }
+
+  public ImmutableSortedMap<String, String> properties() {
+    return ImmutableSortedMap.copyOf(properties);
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof VmSpec) {
+      VmSpec that = (VmSpec) obj;
+      return this.properties.equals(that.properties)
+          && this.options.equals(that.options);
+    } else {
+      return false;
+    }
+  }
+
+  private void initHash() {
+    if (hash == 0) {
+      this.hash = getPersistentHashFunction().hashObject(this, VmSpecFunnel.INSTANCE).asInt();
+    }
+  }
+
+  @Override public int hashCode() {
+    initHash();
+    return hash;
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("properties", properties)
+        .add("options", options)
+        .toString();
+  }
+
+  enum VmSpecFunnel implements Funnel<VmSpec> {
+    INSTANCE;
+
+    @Override public void funnel(VmSpec from, PrimitiveSink into) {
+      StringMapFunnel.INSTANCE.funnel(from.properties, into);
+      StringMapFunnel.INSTANCE.funnel(from.options, into);
+    }
+  }
+
+  public static final class Builder {
+    private final SortedMap<String, String> properties = Maps.newTreeMap();
+    private final SortedMap<String, String> options = Maps.newTreeMap();
+
+    public Builder addOption(String optionName, String value) {
+      this.options.put(optionName, value);
+      return this;
+    }
+
+    public Builder addAllOptions(Map<String, String> options) {
+      this.options.putAll(options);
+      return this;
+    }
+
+    public Builder addProperty(String property, String value) {
+      this.properties.put(property, value);
+      return this;
+    }
+
+    public Builder addAllProperties(Map<String, String> properties) {
+      this.properties.putAll(properties);
+      return this;
+    }
+
+    public VmSpec build() {
+      return new VmSpec(this);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/model/package-info.java b/caliper/src/main/java/com/google/caliper/model/package-info.java
new file mode 100644
index 0000000..882ca26
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/model/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/**
+ * These classes model the data that is collected by the caliper {@linkplain
+ * com.google.caliper.runner runner}: the record of which scenarios were tested on which VMs by
+ * which instruments and, most importantly, all the measurements that were observed.
+ *
+ * <p>The primary goal of these classes is to be as easily convertible back and forth to JSON text
+ * as possible. The secondary goal is to be easily persistable in a relational database.
+ */
+package com.google.caliper.model;
\ No newline at end of file
diff --git a/caliper/src/main/java/com/google/caliper/options/CaliperDirectory.java b/caliper/src/main/java/com/google/caliper/options/CaliperDirectory.java
new file mode 100644
index 0000000..30f8d92
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/options/CaliperDirectory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 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.options;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/** Binding annotation for the directory that caliper should use to store data (--directory). */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+public @interface CaliperDirectory {}
diff --git a/caliper/src/main/java/com/google/caliper/options/CaliperOptions.java b/caliper/src/main/java/com/google/caliper/options/CaliperOptions.java
new file mode 100644
index 0000000..86b96e7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/options/CaliperOptions.java
@@ -0,0 +1,39 @@
+/*
+ * 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.options;
+
+import com.google.caliper.util.ShortDuration;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+
+import java.io.File;
+
+public interface CaliperOptions {
+  String benchmarkClassName();
+  ImmutableSet<String> benchmarkMethodNames();
+  ImmutableSet<String> vmNames();
+  ImmutableSetMultimap<String, String> userParameters();
+  ImmutableSetMultimap<String, String> vmArguments();
+  ImmutableMap<String, String> configProperties();
+  ImmutableSet<String> instrumentNames();
+  int trialsPerScenario();
+  ShortDuration timeLimit();
+  String runName();
+  boolean printConfiguration();
+  boolean dryRun();
+  File caliperDirectory();
+  File caliperConfigFile();
+}
diff --git a/caliper/src/main/java/com/google/caliper/options/CommandLineParser.java b/caliper/src/main/java/com/google/caliper/options/CommandLineParser.java
new file mode 100644
index 0000000..5e67904
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/options/CommandLineParser.java
@@ -0,0 +1,444 @@
+/*
+ * 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.options;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.caliper.util.DisplayUsageException;
+import com.google.caliper.util.InvalidCommandException;
+import com.google.caliper.util.Parser;
+import com.google.caliper.util.Parsers;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.google.common.primitives.Primitives;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.text.ParseException;
+import java.util.Iterator;
+import java.util.List;
+
+// based on r135 of OptionParser.java from vogar
+// NOTE: this class is still pretty messy but will be cleaned up further and possibly offered to
+// Guava.
+
+/**
+ * Parses command line options.
+ *
+ * Strings in the passed-in String[] are parsed left-to-right. Each String is classified as a short
+ * option (such as "-v"), a long option (such as "--verbose"), an argument to an option (such as
+ * "out.txt" in "-f out.txt"), or a non-option positional argument.
+ *
+ * A simple short option is a "-" followed by a short option character. If the option requires an
+ * argument (which is true of any non-boolean option), it may be written as a separate parameter,
+ * but need not be. That is, "-f out.txt" and "-fout.txt" are both acceptable.
+ *
+ * It is possible to specify multiple short options after a single "-" as long as all (except
+ * possibly the last) do not require arguments.
+ *
+ * A long option begins with "--" followed by several characters. If the option requires an
+ * argument, it may be written directly after the option name, separated by "=", or as the next
+ * argument. (That is, "--file=out.txt" or "--file out.txt".)
+ *
+ * A boolean long option '--name' automatically gets a '--no-name' companion. Given an option
+ * "--flag", then, "--flag", "--no-flag", "--flag=true" and "--flag=false" are all valid, though
+ * neither "--flag true" nor "--flag false" are allowed (since "--flag" by itself is sufficient, the
+ * following "true" or "false" is interpreted separately). You can use "yes" and "no" as synonyms
+ * for "true" and "false".
+ *
+ * Each String not starting with a "-" and not a required argument of a previous option is a
+ * non-option positional argument, as are all successive Strings. Each String after a "--" is a
+ * non-option positional argument.
+ *
+ * The fields corresponding to options are updated as their options are processed. Any remaining
+ * positional arguments are returned as an ImmutableList<String>.
+ *
+ * Here's a simple example:
+ *
+ * // This doesn't need to be a separate class, if your application doesn't warrant it. //
+ * Non-@Option fields will be ignored. class Options {
+ *
+ * @Option(names = { "-q", "--quiet" }) boolean quiet = false;
+ *
+ * // Boolean options require a long name if it's to be possible to explicitly turn them off. //
+ * Here the user can use --no-color.
+ * @Option(names = { "--color" }) boolean color = true;
+ * @Option(names = { "-m", "--mode" }) String mode = "standard; // Supply a default just by setting
+ * the field.
+ * @Option(names = { "-p", "--port" }) int portNumber = 8888;
+ *
+ * // There's no need to offer a short name for rarely-used options.
+ * @Option(names = { "--timeout" }) double timeout = 1.0;
+ * @Option(names = { "-o", "--output-file" }) String outputFile;
+ *
+ * }
+ *
+ * See also:
+ *
+ * the getopt(1) man page Python's "optparse" module (http://docs.python.org/library/optparse.html)
+ * the POSIX "Utility Syntax Guidelines" (http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap12.html#tag_12_02)
+ * the GNU "Standards for Command Line Interfaces" (http://www.gnu.org/prep/standards/standards.html#Command_002dLine-Interfaces)
+ */
+final class CommandLineParser<T> {
+  /**
+   * Annotates a field or method in an options class to signify that parsed values should be
+   * injected.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.FIELD, ElementType.METHOD})
+  public @interface Option {
+    /**
+     * The names for this option, such as { "-h", "--help" }. Names must start with one or two '-'s.
+     * An option must have at least one name.
+     */
+    String[] value();
+  }
+
+  /**
+   * Annotates a single method in an options class to receive any "leftover" arguments. The method
+   * must accept {@code ImmutableList<String>} or a supertype. The method will be invoked even if
+   * the list is empty.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.FIELD, ElementType.METHOD})
+  public @interface Leftovers {}
+
+  public static <T> CommandLineParser<T> forClass(Class<? extends T> c) {
+    return new CommandLineParser<T>(c);
+  }
+
+  private final InjectionMap injectionMap;
+  private T injectee;
+
+  // TODO(kevinb): make a helper object that can be mutated during processing
+  private final List<PendingInjection> pendingInjections = Lists.newArrayList();
+
+  /**
+   * Constructs a new command-line parser that will inject values into {@code injectee}.
+   *
+   * @throws IllegalArgumentException if {@code injectee} contains multiple options using the same
+   *     name
+   */
+  private CommandLineParser(Class<? extends T> c) {
+    this.injectionMap = InjectionMap.forClass(c);
+  }
+
+  /**
+   * Parses the command-line arguments 'args', setting the @Option fields of the 'optionSource'
+   * provided to the constructor. Returns a list of the positional arguments left over after
+   * processing all options.
+   */
+  public void parseAndInject(String[] args, T injectee) throws InvalidCommandException {
+    this.injectee = injectee;
+    pendingInjections.clear();
+    Iterator<String> argsIter = Iterators.forArray(args);
+    ImmutableList.Builder<String> builder = ImmutableList.builder();
+
+    while (argsIter.hasNext()) {
+      String arg = argsIter.next();
+      if (arg.equals("--")) {
+        break; // "--" marks the end of options and the beginning of positional arguments.
+      } else if (arg.startsWith("--")) {
+        parseLongOption(arg, argsIter);
+      } else if (arg.startsWith("-")) {
+        parseShortOptions(arg, argsIter);
+      } else {
+        builder.add(arg);
+        // allow positional arguments to mix with options since many linux commands do
+      }
+    }
+
+    for (PendingInjection pi : pendingInjections) {
+      pi.injectableOption.inject(pi.value, injectee);
+    }
+
+    ImmutableList<String> leftovers = builder.addAll(argsIter).build();
+    invokeMethod(injectee, injectionMap.leftoversMethod, leftovers);
+  }
+
+  // Private stuff from here on down
+
+  private abstract static class InjectableOption {
+    abstract boolean isBoolean();
+    abstract void inject(String valueText, Object injectee) throws InvalidCommandException;
+    boolean delayedInjection() {
+      return false;
+    }
+  }
+
+  private static class InjectionMap {
+    public static InjectionMap forClass(Class<?> injectedClass) {
+      ImmutableMap.Builder<String, InjectableOption> builder = ImmutableMap.builder();
+
+      InjectableOption helpOption = new InjectableOption() {
+        @Override boolean isBoolean() {
+          return true;
+        }
+        @Override void inject(String valueText, Object injectee) throws DisplayUsageException {
+          throw new DisplayUsageException();
+        }
+      };
+      builder.put("-h", helpOption);
+      builder.put("--help", helpOption);
+
+      Method leftoverMethod = null;
+
+      for (Field field : injectedClass.getDeclaredFields()) {
+        checkArgument(!field.isAnnotationPresent(Leftovers.class),
+            "Sorry, @Leftovers only works for methods at present"); // TODO(kevinb)
+        Option option = field.getAnnotation(Option.class);
+        if (option != null) {
+          InjectableOption injectable = FieldOption.create(field);
+          for (String optionName : option.value()) {
+            builder.put(optionName, injectable);
+          }
+        }
+      }
+      for (Method method : injectedClass.getDeclaredMethods()) {
+        if (method.isAnnotationPresent(Leftovers.class)) {
+          checkArgument(!isStaticOrAbstract(method),
+              "@Leftovers method cannot be static or abstract");
+          checkArgument(!method.isAnnotationPresent(Option.class),
+              "method has both @Option and @Leftovers");
+          checkArgument(leftoverMethod == null, "Two methods have @Leftovers");
+
+          method.setAccessible(true);
+          leftoverMethod = method;
+
+          // TODO: check type is a supertype of ImmutableList<String>
+        }
+        Option option = method.getAnnotation(Option.class);
+        if (option != null) {
+          InjectableOption injectable = MethodOption.create(method);
+          for (String optionName : option.value()) {
+            builder.put(optionName, injectable);
+          }
+        }
+      }
+
+      ImmutableMap<String, InjectableOption> optionMap = builder.build();
+      return new InjectionMap(optionMap, leftoverMethod);
+    }
+
+    final ImmutableMap<String, InjectableOption> optionMap;
+    final Method leftoversMethod;
+
+    InjectionMap(ImmutableMap<String, InjectableOption> optionMap, Method leftoversMethod) {
+      this.optionMap = optionMap;
+      this.leftoversMethod = leftoversMethod;
+    }
+
+    InjectableOption getInjectableOption(String optionName) throws InvalidCommandException {
+      InjectableOption injectable = optionMap.get(optionName);
+      if (injectable == null) {
+        throw new InvalidCommandException("Invalid option: %s", optionName);
+      }
+      return injectable;
+    }
+  }
+
+  private static class FieldOption extends InjectableOption {
+    private static InjectableOption create(Field field) {
+      field.setAccessible(true);
+      Type type = field.getGenericType();
+
+      if (type instanceof Class) {
+        return new FieldOption(field, (Class<?>) type);
+      }
+      throw new IllegalArgumentException("can't inject parameterized types etc.");
+    }
+
+    private Field field;
+    private boolean isBoolean;
+    private Parser<?> parser;
+
+    private FieldOption(Field field, Class<?> c) {
+      this.field = field;
+      this.isBoolean = c == boolean.class || c == Boolean.class;
+      try {
+        this.parser = Parsers.conventionalParser(Primitives.wrap(c));
+      } catch (NoSuchMethodException e) {
+        throw new IllegalArgumentException("No suitable String-conversion method");
+      }
+    }
+
+    @Override boolean isBoolean() {
+      return isBoolean;
+    }
+
+    @Override void inject(String valueText, Object injectee) throws InvalidCommandException {
+      Object value = convert(parser, valueText);
+      try {
+        field.set(injectee, value);
+      } catch (IllegalAccessException impossible) {
+        throw new AssertionError(impossible);
+      }
+    }
+  }
+
+  private static class MethodOption extends InjectableOption {
+    private static InjectableOption create(Method method) {
+      checkArgument(!isStaticOrAbstract(method),
+          "@Option methods cannot be static or abstract");
+      Class<?>[] classes = method.getParameterTypes();
+      checkArgument(classes.length == 1, "Method does not have exactly one argument: " + method);
+      return new MethodOption(method, classes[0]);
+    }
+
+    private Method method;
+    private boolean isBoolean;
+    private Parser<?> parser;
+
+    private MethodOption(Method method, Class<?> c) {
+      this.method = method;
+      this.isBoolean = c == boolean.class || c == Boolean.class;
+      try {
+        this.parser = Parsers.conventionalParser(Primitives.wrap(c));
+      } catch (NoSuchMethodException e) {
+        throw new IllegalArgumentException("No suitable String-conversion method");
+      }
+
+      method.setAccessible(true);
+    }
+
+    @Override boolean isBoolean() {
+      return isBoolean;
+    }
+
+    @Override boolean delayedInjection() {
+      return true;
+    }
+
+    @Override void inject(String valueText, Object injectee) throws InvalidCommandException {
+      invokeMethod(injectee, method, convert(parser, valueText));
+    }
+  }
+
+  private static Object convert(Parser<?> parser, String valueText) throws InvalidCommandException {
+    Object value;
+    try {
+      value = parser.parse(valueText);
+    } catch (ParseException e) {
+      throw new InvalidCommandException("wrong datatype: " + e.getMessage());
+    }
+    return value;
+  }
+
+  private void parseLongOption(String arg, Iterator<String> args) throws InvalidCommandException {
+    String name = arg.replaceFirst("^--no-", "--");
+    String value = null;
+
+    // Support "--name=value" as well as "--name value".
+    int equalsIndex = name.indexOf('=');
+    if (equalsIndex != -1) {
+      value = name.substring(equalsIndex + 1);
+      name = name.substring(0, equalsIndex);
+    }
+
+    InjectableOption injectable = injectionMap.getInjectableOption(name);
+
+    if (value == null) {
+      value = injectable.isBoolean()
+          ? Boolean.toString(!arg.startsWith("--no-"))
+          : grabNextValue(args, name);
+    }
+    injectNowOrLater(injectable, value);
+  }
+
+  private void injectNowOrLater(InjectableOption injectable, String value)
+      throws InvalidCommandException {
+    if (injectable.delayedInjection()) {
+      pendingInjections.add(new PendingInjection(injectable, value));
+    } else {
+      injectable.inject(value, injectee);
+    }
+  }
+
+  private static class PendingInjection {
+    InjectableOption injectableOption;
+    String value;
+
+    private PendingInjection(InjectableOption injectableOption, String value) {
+      this.injectableOption = injectableOption;
+      this.value = value;
+    }
+  }
+
+  // Given boolean options a and b, and non-boolean option f, we want to allow:
+  // -ab
+  // -abf out.txt
+  // -abfout.txt
+  // (But not -abf=out.txt --- POSIX doesn't mention that either way, but GNU expressly forbids it.)
+
+  private void parseShortOptions(String arg, Iterator<String> args) throws InvalidCommandException {
+    for (int i = 1; i < arg.length(); ++i) {
+      String name = "-" + arg.charAt(i);
+      InjectableOption injectable = injectionMap.getInjectableOption(name);
+
+      String value;
+      if (injectable.isBoolean()) {
+        value = "true";
+      } else {
+        // We need a value. If there's anything left, we take the rest of this "short option".
+        if (i + 1 < arg.length()) {
+          value = arg.substring(i + 1);
+          i = arg.length() - 1; // delayed "break"
+
+        // otherwise the next arg
+        } else {
+          value = grabNextValue(args, name);
+        }
+      }
+      injectNowOrLater(injectable, value);
+    }
+  }
+
+  private static void invokeMethod(Object injectee, Method method, Object value)
+      throws InvalidCommandException {
+    try {
+      method.invoke(injectee, value);
+    } catch (IllegalAccessException impossible) {
+      throw new AssertionError(impossible);
+    } catch (InvocationTargetException e) {
+      Throwable cause = e.getCause();
+      Throwables.propagateIfPossible(cause, InvalidCommandException.class);
+      throw new RuntimeException(e);
+    }
+  }
+
+  private String grabNextValue(Iterator<String> args, String name)
+      throws InvalidCommandException {
+    if (args.hasNext()) {
+      return args.next();
+    } else {
+      throw new InvalidCommandException("option '" + name + "' requires an argument");
+    }
+  }
+
+  private static boolean isStaticOrAbstract(Method method) {
+    int modifiers = method.getModifiers();
+    return Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/options/OptionsModule.java b/caliper/src/main/java/com/google/caliper/options/OptionsModule.java
new file mode 100644
index 0000000..9647475
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/options/OptionsModule.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 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.options;
+
+import com.google.caliper.util.InvalidCommandException;
+
+import dagger.Module;
+import dagger.Provides;
+
+import java.io.File;
+
+import javax.inject.Singleton;
+
+/**
+ * Bindings for Caliper command line options.
+ */
+@Module
+public final class OptionsModule {
+
+  private static final String[] EMPTY_ARGS = new String[] {};
+
+  private final String[] args;
+
+  private boolean requireBenchmarkClassName;
+
+  /**
+   * Return a module that will provide access to configuration options and the name of the
+   * benchmark class.
+   *
+   * @param args the arguments from which the configuration options and the benchmark class name
+   *     are parsed; must have one non-option value that is the benchmark class name.
+   */
+  public static OptionsModule withBenchmarkClass(String [] args) {
+    return new OptionsModule(args, true);
+  }
+
+  /**
+   * Return a module that will provide access to configuration options without the name of the
+   * benchmark class.
+   *
+   * @param args the arguments from which the configuration options are parsed; it must have no
+   *     non-option values.
+   */
+  public static OptionsModule withoutBenchmarkClass(String [] args) {
+    return new OptionsModule(args, false);
+  }
+
+  /**
+   * Return a module that will provide access to the default configuration options.
+   */
+  public static OptionsModule defaultOptionsModule() {
+    return new OptionsModule(EMPTY_ARGS, false);
+  }
+
+  public OptionsModule(String[] args, boolean requireBenchmarkClassName) {
+    this.args = args.clone(); // defensive copy, just in case
+    this.requireBenchmarkClassName = requireBenchmarkClassName;
+  }
+
+  @Provides
+  @Singleton
+  CaliperOptions provideOptions() throws InvalidCommandException {
+    return ParsedOptions.from(args, requireBenchmarkClassName);
+  }
+
+  @Provides @CaliperDirectory static File provideCaliperDirectory(CaliperOptions options) {
+    return options.caliperDirectory();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/options/ParsedOptions.java b/caliper/src/main/java/com/google/caliper/options/ParsedOptions.java
new file mode 100644
index 0000000..43dd8f7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/options/ParsedOptions.java
@@ -0,0 +1,417 @@
+/*
+ * 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.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+import com.google.caliper.options.CommandLineParser.Leftovers;
+import com.google.caliper.options.CommandLineParser.Option;
+import com.google.caliper.util.InvalidCommandException;
+import com.google.caliper.util.ShortDuration;
+import com.google.common.base.Joiner;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Optional;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Ordering;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+final class ParsedOptions implements CaliperOptions {
+
+  public static ParsedOptions from(String[] args, boolean requireBenchmarkClassName)
+      throws InvalidCommandException {
+    ParsedOptions options = new ParsedOptions(requireBenchmarkClassName);
+
+    CommandLineParser<ParsedOptions> parser = CommandLineParser.forClass(ParsedOptions.class);
+    try {
+      parser.parseAndInject(args, options);
+    } catch (InvalidCommandException e) {
+      e.setUsage(USAGE);
+      throw e;
+    }
+    return options;
+  }
+
+  /**
+   * True if the benchmark class name is expected as the last argument, false if it is not allowed.
+   */
+  private final boolean requireBenchmarkClassName;
+
+  private ParsedOptions(boolean requireBenchmarkClassName) {
+    this.requireBenchmarkClassName = requireBenchmarkClassName;
+  }
+
+  // --------------------------------------------------------------------------
+  // Dry run -- simple boolean, needs to be checked in some methods
+  // --------------------------------------------------------------------------
+
+  @Option({"-n", "--dry-run"})
+  private boolean dryRun;
+
+  @Override public boolean dryRun() {
+    return dryRun;
+  }
+
+  private void dryRunIncompatible(String optionName) throws InvalidCommandException {
+    // This only works because CLP does field injection before method injection
+    if (dryRun) {
+      throw new InvalidCommandException("Option not available in dry-run mode: " + optionName);
+    }
+  }
+
+  // --------------------------------------------------------------------------
+  // Delimiter -- injected early so methods can use it
+  // --------------------------------------------------------------------------
+
+  @Option({"-d", "--delimiter"})
+  private String delimiter = ",";
+
+  private ImmutableSet<String> split(String string) {
+    return ImmutableSet.copyOf(Splitter.on(delimiter).split(string));
+  }
+
+  // --------------------------------------------------------------------------
+  // Benchmark method names to run
+  // --------------------------------------------------------------------------
+
+  private ImmutableSet<String> benchmarkNames = ImmutableSet.of();
+
+  @Option({"-b", "--benchmark"})
+  private void setBenchmarkNames(String benchmarksString) {
+    benchmarkNames = split(benchmarksString);
+  }
+
+  @Override public ImmutableSet<String> benchmarkMethodNames() {
+    return benchmarkNames;
+  }
+
+  // --------------------------------------------------------------------------
+  // Print configuration?
+  // --------------------------------------------------------------------------
+
+  @Option({"-p", "--print-config"})
+  private boolean printConfiguration = false;
+
+  @Override public boolean printConfiguration() {
+    return printConfiguration;
+  }
+
+  // --------------------------------------------------------------------------
+  // Trials
+  // --------------------------------------------------------------------------
+
+  private int trials = 1;
+
+  @Option({"-t", "--trials"})
+  private void setTrials(int trials) throws InvalidCommandException {
+    dryRunIncompatible("trials");
+    if (trials < 1) {
+      throw new InvalidCommandException("trials must be at least 1: " + trials);
+    }
+    this.trials = trials;
+  }
+
+  @Override public int trialsPerScenario() {
+    return trials;
+  }
+
+  // --------------------------------------------------------------------------
+  // Time limit
+  // --------------------------------------------------------------------------
+
+  private ShortDuration runTime = ShortDuration.of(5, MINUTES);
+
+  @Option({"-l", "--time-limit"})
+  private void setTimeLimit(String timeLimitString) throws InvalidCommandException {
+    try {
+      this.runTime = ShortDuration.valueOf(timeLimitString);
+    } catch (IllegalArgumentException e) {
+      throw new InvalidCommandException("Invalid time limit: " + timeLimitString);
+    }
+  }
+
+  @Override public ShortDuration timeLimit() {
+    return runTime;
+  }
+
+  // --------------------------------------------------------------------------
+  // Run name
+  // --------------------------------------------------------------------------
+
+  private String runName = "";
+
+  @Option({"-r", "--run-name"})
+  private void setRunName(String runName) {
+    this.runName = checkNotNull(runName);
+  }
+
+  @Override public String runName() {
+    return runName;
+  }
+
+  // --------------------------------------------------------------------------
+  // VM specifications
+  // --------------------------------------------------------------------------
+
+  private ImmutableSet<String> vmNames = ImmutableSet.of();
+
+  @Option({"-m", "--vm"})
+  private void setVms(String vmsString) throws InvalidCommandException {
+    dryRunIncompatible("vm");
+    vmNames = split(vmsString);
+  }
+
+  @Override public ImmutableSet<String> vmNames() {
+    return vmNames;
+  }
+
+  // --------------------------------------------------------------------------
+  // Measuring instruments to use
+  // --------------------------------------------------------------------------
+
+  private static final ImmutableSet<String> DEFAULT_INSTRUMENT_NAMES =
+      new ImmutableSet.Builder<String>()
+      .add("allocation")
+      .add("runtime")
+      .build();
+
+  private ImmutableSet<String> instrumentNames = DEFAULT_INSTRUMENT_NAMES;
+
+  @Option({"-i", "--instrument"})
+  private void setInstruments(String instrumentsString) {
+    instrumentNames = split(instrumentsString);
+  }
+
+  @Override public ImmutableSet<String> instrumentNames() {
+    return instrumentNames;
+  }
+
+// --------------------------------------------------------------------------
+  // Benchmark parameters
+  // --------------------------------------------------------------------------
+
+  private Multimap<String, String> mutableUserParameters = ArrayListMultimap.create();
+
+  @Option("-D")
+  private void addParameterSpec(String nameAndValues) throws InvalidCommandException {
+    addToMultimap(nameAndValues, mutableUserParameters);
+  }
+
+  @Override public ImmutableSetMultimap<String, String> userParameters() {
+    // de-dup values, but keep in order
+    return new ImmutableSetMultimap.Builder<String, String>()
+        .orderKeysBy(Ordering.natural())
+        .putAll(mutableUserParameters)
+        .build();
+  }
+
+  // --------------------------------------------------------------------------
+  // VM arguments
+  // --------------------------------------------------------------------------
+
+  private Multimap<String, String> mutableVmArguments = ArrayListMultimap.create();
+
+  @Option("-J")
+  private void addVmArgumentsSpec(String nameAndValues) throws InvalidCommandException {
+    dryRunIncompatible("-J");
+    addToMultimap(nameAndValues, mutableVmArguments);
+  }
+
+  @Override public ImmutableSetMultimap<String, String> vmArguments() {
+    // de-dup values, but keep in order
+    return new ImmutableSetMultimap.Builder<String, String>()
+        .orderKeysBy(Ordering.natural())
+        .putAll(mutableVmArguments)
+        .build();
+  }
+
+  // --------------------------------------------------------------------------
+  // VM arguments
+  // --------------------------------------------------------------------------
+
+  private final Map<String, String> mutableConfigPropertes = Maps.newHashMap();
+
+  @Option("-C")
+  private void addConfigProperty(String nameAndValue) throws InvalidCommandException {
+    List<String> tokens = splitProperty(nameAndValue);
+    mutableConfigPropertes.put(tokens.get(0), tokens.get(1));
+  }
+
+  @Override public ImmutableMap<String, String> configProperties() {
+    return ImmutableMap.copyOf(mutableConfigPropertes);
+  }
+
+  // --------------------------------------------------------------------------
+  // Location of .caliper
+  // --------------------------------------------------------------------------
+
+  private File caliperDirectory = new File(System.getProperty("user.home"), ".caliper");
+
+  @Option({"--directory"})
+  private void setCaliperDirectory(String path) {
+    caliperDirectory = new File(path);
+  }
+
+  @Override public File caliperDirectory() {
+    return caliperDirectory;
+  }
+
+  // --------------------------------------------------------------------------
+  // Location of config.properties
+  // --------------------------------------------------------------------------
+
+  private Optional<File> caliperConfigFile = Optional.absent();
+
+  @Option({"-c", "--config"})
+  private void setCaliperConfigFile(String filename) {
+    caliperConfigFile = Optional.of(new File(filename));
+  }
+
+  @Override public File caliperConfigFile() {
+    return caliperConfigFile.or(new File(caliperDirectory, "config.properties"));
+  }
+
+
+  // --------------------------------------------------------------------------
+  // Leftover - benchmark class name
+  // --------------------------------------------------------------------------
+
+  private String benchmarkClassName;
+
+  @Leftovers
+  private void setLeftovers(ImmutableList<String> leftovers) throws InvalidCommandException {
+    if (requireBenchmarkClassName) {
+      if (leftovers.isEmpty()) {
+        throw new InvalidCommandException("No benchmark class specified");
+      }
+      if (leftovers.size() > 1) {
+        throw new InvalidCommandException("Extra stuff, expected only class name: " + leftovers);
+      }
+      this.benchmarkClassName = leftovers.get(0);
+    } else {
+      if (!leftovers.isEmpty()) {
+        throw new InvalidCommandException(
+            "Extra stuff, did not expect non-option arguments: " + leftovers);
+      }
+    }
+  }
+
+  @Override public String benchmarkClassName() {
+    return benchmarkClassName;
+  }
+
+  // --------------------------------------------------------------------------
+  // Helper methods
+  // --------------------------------------------------------------------------
+
+  private static List<String> splitProperty(String propertyString) throws InvalidCommandException {
+    List<String> tokens = ImmutableList.copyOf(Splitter.on('=').limit(2).split(propertyString));
+    if (tokens.size() != 2) {
+      throw new InvalidCommandException("no '=' found in: " + propertyString);
+    }
+    return tokens;
+  }
+
+  private void addToMultimap(String nameAndValues, Multimap<String, String> multimap)
+      throws InvalidCommandException {
+    List<String> tokens = splitProperty(nameAndValues);
+    String name = tokens.get(0);
+    String values = tokens.get(1);
+
+    if (multimap.containsKey(name)) {
+      throw new InvalidCommandException("multiple parameter sets for: " + name);
+    }
+    multimap.putAll(name, split(values));
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("benchmarkClassName", this.benchmarkClassName())
+        .add("benchmarkMethodNames", this.benchmarkMethodNames())
+        .add("benchmarkParameters", this.userParameters())
+        .add("dryRun", this.dryRun())
+        .add("instrumentNames", this.instrumentNames())
+        .add("vms", this.vmNames())
+        .add("vmArguments", this.vmArguments())
+        .add("trials", this.trialsPerScenario())
+        .add("printConfig", this.printConfiguration())
+        .add("delimiter", this.delimiter)
+        .add("caliperConfigFile", this.caliperConfigFile)
+        .toString();
+  }
+
+  // --------------------------------------------------------------------------
+  // Usage
+  // --------------------------------------------------------------------------
+
+  // TODO(kevinb): kinda nice if CommandLineParser could autogenerate most of this...
+  // TODO(kevinb): a test could actually check that we don't exceed 79 columns.
+  private static final ImmutableList<String> USAGE = ImmutableList.of(
+      "Usage:",
+      " java com.google.caliper.runner.CaliperMain <benchmark_class_name> [options...]",
+      "",
+      "Options:",
+      " -h, --help         print this message",
+      " -n, --dry-run      instead of measuring, execute a single rep for each scenario",
+      "                    in-process",
+      " -b, --benchmark    comma-separated list of benchmark methods to run; 'foo' is",
+      "                    an alias for 'timeFoo' (default: all found in class)",
+      " -m, --vm           comma-separated list of VMs to test on; possible values are",
+      "                    configured in Caliper's configuration file (default:",
+      "                    whichever VM caliper itself is running in, only)",
+      " -i, --instrument   comma-separated list of measuring instruments to use; possible ",
+      "                    values are configured in Caliper's configuration file ",
+      "                    (default: \"" + Joiner.on(",").join(DEFAULT_INSTRUMENT_NAMES) + "\")",
+      " -t, --trials       number of independent trials to peform per benchmark scenario; ",
+      "                    a positive integer (default: 1)",
+      " -l, --time-limit   maximum length of time allowed for a single trial; use 0 to allow ",
+      "                    trials to run indefinitely. (default: 30s) ",
+      " -r, --run-name     a user-friendly string used to identify the run",
+      " -p, --print-config print the effective configuration that will be used by Caliper",
+      " -d, --delimiter    separator used in options that take multiple values (default: ',')",
+      " -c, --config       location of Caliper's configuration file (default:",
+      "                    $HOME/.caliper/config.properties)",
+      " --directory        location of Caliper's configuration and data directory ",
+      "                    (default: $HOME/.caliper)",
+      "",
+      " -Dparam=val1,val2,...",
+      "     Specifies the values to inject into the 'param' field of the benchmark",
+      "     class; if multiple values or parameters are specified in this way, caliper",
+      "     will try all possible combinations.",
+      "",
+      // commented out until this flag is fixed
+      // " -JdisplayName='vm arg list choice 1,vm arg list choice 2,...'",
+      // "     Specifies alternate sets of VM arguments to pass. As with any variable,",
+      // "     caliper will test all possible combinations. Example:",
+      // "     -Jmemory='-Xms32m -Xmx32m,-Xms512m -Xmx512m'",
+      // "",
+      " -CconfigProperty=value",
+      "     Specifies a value for any property that could otherwise be specified in ",
+      "     $HOME/.caliper/config.properties. Properties specified on the command line",
+      "     will override those specified in the file.",
+      "",
+      "See http://code.google.com/p/caliper/wiki/CommandLineOptions for more details.",
+      "");
+}
diff --git a/caliper/src/main/java/com/google/caliper/options/package-info.java b/caliper/src/main/java/com/google/caliper/options/package-info.java
new file mode 100644
index 0000000..b0bba1f
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/options/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/**
+ * Contains code relating to parsing and managing command line options. This is distinct from
+ * configuration ({@code ~/.caliper/config.properties}), which lives in the
+ * {@link com.google.caliper.config} package.
+ */
+package com.google.caliper.options;
diff --git a/caliper/src/main/java/com/google/caliper/platform/Platform.java b/caliper/src/main/java/com/google/caliper/platform/Platform.java
new file mode 100644
index 0000000..44b6cd2
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/platform/Platform.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 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.platform;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * An abstraction of the platform within which caliper (both the scheduler and the actual workers)
+ * will run.
+ */
+public abstract class Platform {
+
+  private final Platform.Type platformType;
+
+  public Platform(Type platformType) {
+    this.platformType = checkNotNull(platformType);
+  }
+
+  /**
+   * Get the executable for the virtual machine for this platform.
+   *
+   * @param vmHome the home directory of the virtual machine, allows testing across multiple vms on
+   *     the same platform in one go.
+   */
+  public abstract File vmExecutable(File vmHome);
+
+  /**
+   * Additional virtual machine arguments common to all instruments that are passed to a worker.
+   */
+  public abstract ImmutableSet<String> commonInstrumentVmArgs();
+
+  /**
+   * The name of the platform type.
+   */
+  public String name() {
+    return platformType.name;
+  }
+
+  /**
+   * Additional arguments that should be passed to a worker.
+   */
+  public abstract ImmutableSet<String> workerProcessArgs();
+
+  /**
+   * The class path that should be used to run a worker..
+   */
+  public abstract String workerClassPath();
+
+  /**
+   * Checks to see whether the specific class is supported on this platform.
+   *
+   * <p>This checks to see whether {@link SupportedPlatform} specifies a {@link Type} that
+   * matches this platform.
+   *
+   * @param clazz the class to check.
+   * @return true if it is supported, false otherwise.
+   */
+  public boolean supports(Class<?> clazz) {
+    SupportedPlatform annotation = clazz.getAnnotation(SupportedPlatform.class);
+    if (annotation == null) {
+      // Support must be explicitly declared.
+      return false;
+    }
+
+    Platform.Type[] types = annotation.value();
+    for (Type type : types) {
+      if (type.equals(platformType)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Get the input arguments for the current running JVM.
+   */
+  public abstract Collection<String> inputArguments();
+
+  /**
+   * Selects the names of properties that will be used to populate the
+   * {@link com.google.caliper.model.VmSpec} for a specific run.
+   */
+  public abstract Predicate<String> vmPropertiesToRetain();
+
+  /**
+   * Checks that the vm options are appropriate for this platform, throws an exception if they are
+   * not.
+   */
+  public abstract void checkVmProperties(Map<String, String> options);
+
+  /**
+   * Get the default vm home directory.
+   */
+  public File defaultVmHomeDir() {
+    // Currently both supported platforms use java.home property to refer to the 'home' directory
+    // of the vm, in the case of Android it is the directory containing the dalvikvm executable.
+    return new File(System.getProperty("java.home"));
+  }
+
+  /**
+   * Get the home directory of a custom virtual machine.
+   * @param vmGroupMap the configuration properties for all VMs, may contain default properties that
+   *     apply to all VMs.
+   * @param vmConfigName the name of the VM within the configuration, used to access VM specific
+   *     properties from the {@code vmGroupMap}.
+   * @throws VirtualMachineException if there was a problem with the VM, either the configuration
+   *     or the file system.
+   */
+  public abstract File customVmHomeDir(Map<String, String> vmGroupMap, String vmConfigName)
+          throws VirtualMachineException;
+
+  /**
+   * The type of platforms supported.
+   */
+  public enum Type {
+    DALVIK("Dalvik"),
+    JVM("Java");
+
+    private final String name;
+
+    Type(String name) {
+      this.name = name;
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/platform/SupportedPlatform.java b/caliper/src/main/java/com/google/caliper/platform/SupportedPlatform.java
new file mode 100644
index 0000000..e3ac741
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/platform/SupportedPlatform.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 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.platform;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates the platforms supported by the annotated type.
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+@Documented
+public @interface SupportedPlatform {
+  Platform.Type[] value();
+}
diff --git a/caliper/src/main/java/com/google/caliper/UploadResults.java b/caliper/src/main/java/com/google/caliper/platform/VirtualMachineException.java
similarity index 68%
copy from caliper/src/main/java/com/google/caliper/UploadResults.java
copy to caliper/src/main/java/com/google/caliper/platform/VirtualMachineException.java
index 7dae7af..b241212 100644
--- a/caliper/src/main/java/com/google/caliper/UploadResults.java
+++ b/caliper/src/main/java/com/google/caliper/platform/VirtualMachineException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2015 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,14 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
-
-import java.io.File;
+package com.google.caliper.platform;
 
 /**
- * Usage: UploadResults <file_or_dir>
+ * Thrown when a problem was found with a custom VM configuration.
  */
-public class UploadResults {
-  public static void main(String[] args) {
-    new Runner().uploadResultsFileOrDir(new File(args[0]));
+public class VirtualMachineException extends Exception {
+
+  public VirtualMachineException(String message) {
+    super(message);
   }
 }
diff --git a/caliper/src/main/java/com/google/caliper/platform/dalvik/DalvikModule.java b/caliper/src/main/java/com/google/caliper/platform/dalvik/DalvikModule.java
new file mode 100644
index 0000000..45c5b00
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/platform/dalvik/DalvikModule.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 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.platform.dalvik;
+
+import com.google.common.base.Optional;
+
+import dagger.Module;
+import dagger.Provides;
+
+import javax.inject.Singleton;
+
+/**
+ * Provider of a {@link DalvikPlatform} instance.
+ *
+ * <p>The {@link DalvikPlatform} is optional as it is only available when running on Android.
+ */
+@Module
+public final class DalvikModule {
+
+  @Provides
+  @Singleton
+  public static Optional<DalvikPlatform> provideOptionalPlatform() {
+    if (System.getProperty("java.specification.name").equals("Dalvik Core Library")) {
+      return Optional.of(new DalvikPlatform());
+    } else {
+      return Optional.absent();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/platform/dalvik/DalvikPlatform.java b/caliper/src/main/java/com/google/caliper/platform/dalvik/DalvikPlatform.java
new file mode 100644
index 0000000..9a971d3
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/platform/dalvik/DalvikPlatform.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 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.platform.dalvik;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.caliper.platform.Platform;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * An abstraction of the Dalvik (aka Android) platform.
+ *
+ * <p>Although this talks about dalvik it actually works with ART too.
+ */
+public final class DalvikPlatform extends Platform {
+
+  public DalvikPlatform() {
+    super(Type.DALVIK);
+  }
+
+  @Override
+  public File vmExecutable(File vmHome) {
+    // TODO(user): Allow the 32/64 version of dalvik to be selected rather than the default
+    // standard configurations of Android systems and windows.
+    File bin = new File(vmHome, "bin");
+    Preconditions.checkState(bin.exists() && bin.isDirectory(),
+        "Could not find %s under android root %s", bin, vmHome);
+    String executableName = "dalvikvm";
+    File dalvikvm = new File(bin, executableName);
+    if (!dalvikvm.exists() || dalvikvm.isDirectory()) {
+      throw new IllegalStateException(
+          String.format("Cannot find %s binary in %s", executableName, bin));
+    }
+
+    return dalvikvm;
+  }
+
+  @Override
+  public ImmutableSet<String> commonInstrumentVmArgs() {
+    return ImmutableSet.of();
+  }
+
+  @Override
+  public ImmutableSet<String> workerProcessArgs() {
+    return ImmutableSet.of();
+  }
+
+  @Override
+  public String workerClassPath() {
+    // TODO(user): Find a way to get the class path programmatically from the class loader.
+    return System.getProperty("java.class.path");
+  }
+
+  @Override
+  public Collection<String> inputArguments() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public Predicate<String> vmPropertiesToRetain() {
+    return Predicates.alwaysFalse();
+  }
+
+  @Override
+  public void checkVmProperties(Map<String, String> options) {
+    checkState(options.isEmpty());
+  }
+
+  @Override
+  public File customVmHomeDir(Map<String, String> vmGroupMap, String vmConfigName) {
+    // TODO(user): Should probably use this to support specifying dalvikvm32/dalvikvm64
+    // and maybe even app_process.
+    throw new UnsupportedOperationException(
+            "Running with a custom Dalvik VM is not currently supported");
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/platform/jvm/EffectiveClassPath.java b/caliper/src/main/java/com/google/caliper/platform/jvm/EffectiveClassPath.java
new file mode 100644
index 0000000..8963638
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/platform/jvm/EffectiveClassPath.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2013 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.platform.jvm;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * Provides a class path containing all of the jars present on the local machine that are referenced
+ * by a given {@link ClassLoader}.
+ */
+final class EffectiveClassPath {
+  private EffectiveClassPath() {}
+
+  private static final String PATH_SEPARATOR = System.getProperty("path.separator");
+  private static final String JAVA_CLASS_PATH = System.getProperty("java.class.path");
+
+  static String getClassPathForClassLoader(ClassLoader classLoader) {
+    // Order of JAR files may have some significance. Try to preserve it.
+    LinkedHashSet<File> files = new LinkedHashSet<File>();
+    for (String entry : Splitter.on(PATH_SEPARATOR).split(JAVA_CLASS_PATH)) {
+      files.add(new File(entry));
+    }
+    files.addAll(getClassPathFiles(classLoader));
+
+    return Joiner.on(PATH_SEPARATOR).join(files);
+  }
+
+  private static Set<File> getClassPathFiles(ClassLoader classLoader) {
+    ImmutableSet.Builder<File> files = ImmutableSet.builder();
+    @Nullable ClassLoader parent = classLoader.getParent();
+    if (parent != null) {
+      files.addAll(getClassPathFiles(parent));
+    }
+    if (classLoader instanceof URLClassLoader) {
+      URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
+      for (URL url : urlClassLoader.getURLs()) {
+        try {
+          files.add(new File(url.toURI()));
+        } catch (URISyntaxException e) {
+          // skip it then
+        } catch (IllegalArgumentException e) {
+          // skip it then
+        }
+      }
+    }
+    return files.build();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/UploadResults.java b/caliper/src/main/java/com/google/caliper/platform/jvm/JvmModule.java
similarity index 62%
copy from caliper/src/main/java/com/google/caliper/UploadResults.java
copy to caliper/src/main/java/com/google/caliper/platform/jvm/JvmModule.java
index 7dae7af..6490374 100644
--- a/caliper/src/main/java/com/google/caliper/UploadResults.java
+++ b/caliper/src/main/java/com/google/caliper/platform/jvm/JvmModule.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2015 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,19 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
+package com.google.caliper.platform.jvm;
 
-import java.io.File;
+import dagger.Module;
+import dagger.Provides;
 
 /**
- * Usage: UploadResults <file_or_dir>
+ * Provider of the default {@link JvmPlatform}, this is assumed to always be available.
  */
-public class UploadResults {
-  public static void main(String[] args) {
-    new Runner().uploadResultsFileOrDir(new File(args[0]));
+@Module
+public final class JvmModule {
+
+  @Provides
+  public static JvmPlatform provideJvmPlatform() {
+    return new JvmPlatform();
   }
 }
diff --git a/caliper/src/main/java/com/google/caliper/platform/jvm/JvmPlatform.java b/caliper/src/main/java/com/google/caliper/platform/jvm/JvmPlatform.java
new file mode 100644
index 0000000..549cdd9
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/platform/jvm/JvmPlatform.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2015 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.platform.jvm;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.Thread.currentThread;
+
+import com.google.caliper.platform.Platform;
+import com.google.caliper.platform.VirtualMachineException;
+import com.google.caliper.util.Util;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.util.Collection;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * An abstraction of a standard Java Virtual Machine platform.
+ */
+public final class JvmPlatform extends Platform {
+
+  /**
+   * Some default JVM args to keep worker VMs somewhat predictable.
+   */
+  @VisibleForTesting
+  public static final ImmutableSet<String> INSTRUMENT_JVM_ARGS = ImmutableSet.of(
+      // do compilation serially
+      "-Xbatch",
+      // make sure compilation doesn't run in parallel with itself
+      "-XX:CICompilerCount=1",
+      // ensure the parallel garbage collector
+      "-XX:+UseParallelGC",
+      // generate classes or don't, but do it immediately
+      "-Dsun.reflect.inflationThreshold=0");
+
+  private static final ImmutableSet<String> WORKER_PROCESS_ARGS = ImmutableSet.of(
+      "-XX:+PrintFlagsFinal",
+      "-XX:+PrintCompilation",
+      "-XX:+PrintGC");
+
+
+  private static final Predicate<String> PROPERTIES_TO_RETAIN = new Predicate<String>() {
+    @Override public boolean apply(String input) {
+      return input.startsWith("java.vm")
+          || input.startsWith("java.runtime")
+          || input.equals("java.version")
+          || input.equals("java.vendor")
+          || input.equals("sun.reflect.noInflation")
+          || input.equals("sun.reflect.inflationThreshold");
+    }
+  };
+
+  public JvmPlatform() {
+    super(Type.JVM);
+  }
+
+  @Override
+  public File vmExecutable(File javaHome) {
+    // TODO(gak): support other platforms. This currently supports finding the java executable on
+    // standard configurations of unix systems and windows.
+    File bin = new File(javaHome, "bin");
+    Preconditions.checkState(bin.exists() && bin.isDirectory(),
+        "Could not find %s under java home %s", bin, javaHome);
+    File jvm = new File(bin, "java");
+    if (!jvm.exists() || jvm.isDirectory()) {
+      jvm = new File(bin, "java.exe");
+      if (!jvm.exists() || jvm.isDirectory()) {
+        throw new IllegalStateException(
+            String.format("Cannot find java binary in %s, looked for java and java.exe", bin));
+      }
+    }
+
+    return jvm;
+  }
+
+  @Override
+  public ImmutableSet<String> commonInstrumentVmArgs() {
+    return INSTRUMENT_JVM_ARGS;
+  }
+
+  @Override
+  public ImmutableSet<String> workerProcessArgs() {
+    return WORKER_PROCESS_ARGS;
+  }
+
+  @Override
+  public String workerClassPath() {
+    return getClassPath();
+  }
+
+  private static String getClassPath() {
+    // Use the effective class path in case this is being invoked in an isolated class loader
+    String classpath =
+        EffectiveClassPath.getClassPathForClassLoader(currentThread().getContextClassLoader());
+    return classpath;
+  }
+
+  @Override
+  public Collection<String> inputArguments() {
+    return Collections2.filter(ManagementFactory.getRuntimeMXBean().getInputArguments(),
+        new Predicate<String>() {
+          @Override
+          public boolean apply(String input) {
+            // Exclude the -agentlib:jdwp param which configures the socket debugging protocol.
+            // If this is set in the parent VM we do not want it to be inherited by the child
+            // VM.  If it is, the child will die immediately on startup because it will fail to
+            // bind to the debug port (because the parent VM is already bound to it).
+            return !input.startsWith("-agentlib:jdwp");
+          }
+        });
+  }
+
+  @Override
+  public Predicate<String> vmPropertiesToRetain() {
+    return PROPERTIES_TO_RETAIN;
+  }
+
+  @Override
+  public void checkVmProperties(Map<String, String> options) {
+    checkState(!options.isEmpty());
+  }
+
+  @Override
+  public File customVmHomeDir(Map<String, String> vmGroupMap, String vmConfigName)
+          throws VirtualMachineException {
+    // Configuration can either be:
+    //   vm.<vmConfigName>.home = <homeDir>
+    // or
+    //   vm.baseDirectory = <baseDir>
+    //   homeDir = <baseDir>/<vmConfigName>
+    ImmutableMap<String, String> vmMap = Util.subgroupMap(vmGroupMap, vmConfigName);
+    return getJdkHomeDir(vmGroupMap.get("baseDirectory"), vmMap.get("home"), vmConfigName);
+  }
+
+  // TODO(gak): check that the directory seems to be a jdk home (with a java binary and all of that)
+  // TODO(gak): make this work with different directory layouts.  I'm looking at you OS X...
+  public static File getJdkHomeDir(@Nullable String baseDirectoryPath,
+          @Nullable String homeDirPath, String vmConfigName)
+          throws VirtualMachineException {
+    if (homeDirPath == null) {
+      File baseDirectory = getBaseDirectory(baseDirectoryPath);
+      File homeDir = new File(baseDirectory, vmConfigName);
+      checkConfiguration(homeDir.isDirectory(), "%s is not a directory", homeDir);
+      return homeDir;
+    } else {
+      File potentialHomeDir = new File(homeDirPath);
+      if (potentialHomeDir.isAbsolute()) {
+        checkConfiguration(potentialHomeDir.isDirectory(), "%s is not a directory",
+                potentialHomeDir);
+        return potentialHomeDir;
+      } else {
+        File baseDirectory = getBaseDirectory(baseDirectoryPath);
+        File homeDir = new File(baseDirectory, homeDirPath);
+        checkConfiguration(homeDir.isDirectory(), "%s is not a directory", potentialHomeDir);
+        return homeDir;
+      }
+    }
+  }
+
+  private static File getBaseDirectory(@Nullable String baseDirectoryPath)
+          throws VirtualMachineException {
+    if (baseDirectoryPath == null) {
+      throw new VirtualMachineException(
+              "must set either a home directory or a base directory");
+    } else {
+      File baseDirectory = new File(baseDirectoryPath);
+      checkConfiguration(baseDirectory.isAbsolute(), "base directory cannot be a relative path");
+      checkConfiguration(baseDirectory.isDirectory(), "base directory must be a directory");
+      return baseDirectory;
+    }
+  }
+
+  private static void checkConfiguration(boolean check, String message)
+          throws VirtualMachineException {
+    if (!check) {
+      throw new VirtualMachineException(message);
+    }
+  }
+
+  private static void checkConfiguration(boolean check, String messageFormat, Object... args)
+          throws VirtualMachineException {
+    if (!check) {
+      throw new VirtualMachineException(String.format(messageFormat, args));
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/AllocationInstrument.java b/caliper/src/main/java/com/google/caliper/runner/AllocationInstrument.java
new file mode 100644
index 0000000..7469117
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/AllocationInstrument.java
@@ -0,0 +1,229 @@
+/*
+ * 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.runner;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Throwables.propagateIfInstanceOf;
+import static java.util.logging.Level.SEVERE;
+
+import com.google.caliper.Benchmark;
+import com.google.caliper.api.SkipThisScenarioException;
+import com.google.caliper.config.VmConfig;
+import com.google.caliper.platform.Platform;
+import com.google.caliper.platform.SupportedPlatform;
+import com.google.caliper.worker.MacrobenchmarkAllocationWorker;
+import com.google.caliper.worker.MicrobenchmarkAllocationWorker;
+import com.google.caliper.worker.Worker;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.monitoring.runtime.instrumentation.AllocationInstrumenter;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.logging.Logger;
+
+/**
+ * {@link Instrument} that watches the memory allocations in an invocation of the
+ * benchmark method and reports some statistic. The benchmark method must accept a
+ * single int argument 'reps', which is the number of times to execute the guts of
+ * the benchmark method, and it must be public and non-static.
+ *
+ * <p>Note that the allocation instruments reports a "worst case" for allocation in that it reports
+ * the bytes and objects allocated in interpreted mode (no JIT).
+ */
+@SupportedPlatform(Platform.Type.JVM)
+public final class AllocationInstrument extends Instrument {
+  private static final String ALLOCATION_AGENT_JAR_OPTION = "allocationAgentJar";
+  /**
+   * If this option is set to {@code true} then every individual allocation will be tracked and
+   * logged.  This will also increase the detail of certain error messages.
+   */
+  private static final String TRACK_ALLOCATIONS_OPTION = "trackAllocations";
+  private static final Logger logger = Logger.getLogger(AllocationInstrument.class.getName());
+
+  @Override
+  public boolean isBenchmarkMethod(Method method) {
+    return method.isAnnotationPresent(Benchmark.class) || BenchmarkMethods.isTimeMethod(method);
+  }
+
+  @Override
+  public Instrumentation createInstrumentation(Method benchmarkMethod)
+      throws InvalidBenchmarkException {
+    checkNotNull(benchmarkMethod);
+    checkArgument(isBenchmarkMethod(benchmarkMethod));
+    try {
+      switch (BenchmarkMethods.Type.of(benchmarkMethod)) {
+        case MACRO:
+          return new MacroAllocationInstrumentation(benchmarkMethod);
+        case MICRO:
+        case PICO:
+          return new MicroAllocationInstrumentation(benchmarkMethod);
+        default:
+          throw new AssertionError("unknown type");
+      }
+    } catch (IllegalArgumentException e) {
+      throw new InvalidBenchmarkException("Benchmark methods must have no arguments or accept "
+          + "a single int or long parameter: %s", benchmarkMethod.getName());
+    }
+  }
+
+  private final class MicroAllocationInstrumentation extends Instrumentation {
+    MicroAllocationInstrumentation(Method benchmarkMethod) {
+      super(benchmarkMethod);
+    }
+
+    @Override
+    public void dryRun(Object benchmark) throws UserCodeException {
+      // execute the benchmark method, but don't try to take any measurements, because this JVM
+      // may not have the allocation instrumenter agent.
+      try {
+        benchmarkMethod.invoke(benchmark, 1);
+      } catch (IllegalAccessException impossible) {
+        throw new AssertionError(impossible);
+      } catch (InvocationTargetException e) {
+        Throwable userException = e.getCause();
+        propagateIfInstanceOf(userException, SkipThisScenarioException.class);
+        throw new UserCodeException(userException);
+      }
+    }
+
+    @Override public ImmutableMap<String, String> workerOptions() {
+      return ImmutableMap.of(TRACK_ALLOCATIONS_OPTION, options.get(TRACK_ALLOCATIONS_OPTION));
+    }
+
+    @Override
+    public Class<? extends Worker> workerClass() {
+      return MicrobenchmarkAllocationWorker.class;
+    }
+
+    @Override
+    MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
+      return new Instrument.DefaultMeasurementCollectingVisitor(
+          ImmutableSet.of("bytes", "objects"));
+    }
+  }
+
+  @Override public TrialSchedulingPolicy schedulingPolicy() {
+    // Assuming there is enough memory it should be fine to run these in parallel.
+    return TrialSchedulingPolicy.PARALLEL;
+  }
+
+  private final class MacroAllocationInstrumentation extends Instrumentation {
+    MacroAllocationInstrumentation(Method benchmarkMethod) {
+      super(benchmarkMethod);
+    }
+
+    @Override
+    public void dryRun(Object benchmark) throws InvalidBenchmarkException {
+      // execute the benchmark method, but don't try to take any measurements, because this JVM
+      // may not have the allocation instrumenter agent.
+      try {
+        benchmarkMethod.invoke(benchmark);
+      } catch (IllegalAccessException impossible) {
+        throw new AssertionError(impossible);
+      } catch (InvocationTargetException e) {
+        Throwable userException = e.getCause();
+        propagateIfInstanceOf(userException, SkipThisScenarioException.class);
+        throw new UserCodeException(userException);
+      }
+    }
+
+    @Override public ImmutableMap<String, String> workerOptions() {
+      return ImmutableMap.of(TRACK_ALLOCATIONS_OPTION, options.get(TRACK_ALLOCATIONS_OPTION));
+    }
+
+    @Override
+    public Class<? extends Worker> workerClass() {
+      return MacrobenchmarkAllocationWorker.class;
+    }
+
+    @Override
+    MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
+      return new Instrument.DefaultMeasurementCollectingVisitor(
+          ImmutableSet.of("bytes", "objects"));
+    }
+  }
+
+  @Override
+  public ImmutableSet<String> instrumentOptions() {
+    return ImmutableSet.of(ALLOCATION_AGENT_JAR_OPTION, TRACK_ALLOCATIONS_OPTION);
+  }
+
+  private static Optional<File> findAllocationInstrumentJarOnClasspath() throws IOException {
+    ImmutableSet<File> jarFiles = JarFinder.findJarFiles(
+        Thread.currentThread().getContextClassLoader(),
+        ClassLoader.getSystemClassLoader());
+    for (File file : jarFiles) {
+      JarFile jarFile = null;
+      try {
+        jarFile = new JarFile(file);
+        Manifest manifest = jarFile.getManifest();
+        if ((manifest != null)
+            && AllocationInstrumenter.class.getName().equals(
+                manifest.getMainAttributes().getValue("Premain-Class"))) {
+          return Optional.of(file);
+        }
+      } finally {
+        if (jarFile != null) {
+          jarFile.close();
+        }
+      }
+    }
+    return Optional.absent();
+  }
+
+  /**
+   * This instrument's worker requires the allocationinstrumenter agent jar, specified
+   * on the worker VM's command line with "-javaagent:[jarfile]".
+   */
+  @Override ImmutableSet<String> getExtraCommandLineArgs(VmConfig vmConfig) {
+    String agentJar = options.get(ALLOCATION_AGENT_JAR_OPTION);
+    if (Strings.isNullOrEmpty(agentJar)) {
+      try {
+        Optional<File> instrumentJar = findAllocationInstrumentJarOnClasspath();
+        // TODO(gak): bundle up the allocation jar and unpack it if it's not on the classpath
+        if (instrumentJar.isPresent()) {
+          agentJar = instrumentJar.get().getAbsolutePath();
+        }
+      } catch (IOException e) {
+        logger.log(SEVERE,
+            "An exception occurred trying to locate the allocation agent jar on the classpath", e);
+      }
+    }
+    if (Strings.isNullOrEmpty(agentJar) || !new File(agentJar).exists()) {
+      throw new IllegalStateException("Can't find required allocationinstrumenter agent jar");
+    }
+    // Add microbenchmark args to minimize differences in the output
+    return new ImmutableSet.Builder<String>()
+        .addAll(super.getExtraCommandLineArgs(vmConfig))
+        // we just run in interpreted mode to ensure that intrinsics don't break the instrumentation
+        .add("-Xint")
+        .add("-javaagent:" + agentJar)
+        // Some environments rename files and use symlinks to improve resource caching,
+        // if the agent jar path is actually a symlink it will prevent the agent from finding itself
+        // and adding itself to the bootclasspath, so we do it manually here.
+        .add("-Xbootclasspath/a:" + agentJar)
+        .build();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ArbitraryMeasurementInstrument.java b/caliper/src/main/java/com/google/caliper/runner/ArbitraryMeasurementInstrument.java
new file mode 100644
index 0000000..2845cd8
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ArbitraryMeasurementInstrument.java
@@ -0,0 +1,152 @@
+/*
+ * 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.runner;
+
+import static com.google.caliper.runner.CommonInstrumentOptions.GC_BEFORE_EACH_OPTION;
+import static com.google.common.base.Throwables.propagateIfInstanceOf;
+
+import com.google.caliper.api.SkipThisScenarioException;
+import com.google.caliper.bridge.AbstractLogMessageVisitor;
+import com.google.caliper.bridge.StopMeasurementLogMessage;
+import com.google.caliper.model.ArbitraryMeasurement;
+import com.google.caliper.model.Measurement;
+import com.google.caliper.platform.Platform;
+import com.google.caliper.platform.SupportedPlatform;
+import com.google.caliper.util.Util;
+import com.google.caliper.worker.ArbitraryMeasurementWorker;
+import com.google.caliper.worker.Worker;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Instrument for taking an arbitrary measurement. When using this instrument, the benchmark code
+ * itself returns the value. See {@link ArbitraryMeasurement}.
+ */
+@SupportedPlatform(Platform.Type.JVM)
+public final class ArbitraryMeasurementInstrument extends Instrument {
+  @Override public boolean isBenchmarkMethod(Method method) {
+    return method.isAnnotationPresent(ArbitraryMeasurement.class);
+  }
+
+  @Override
+  public Instrumentation createInstrumentation(Method benchmarkMethod)
+      throws InvalidBenchmarkException {
+    if (benchmarkMethod.getParameterTypes().length != 0) {
+      throw new InvalidBenchmarkException(
+          "Arbitrary measurement methods should take no parameters: " + benchmarkMethod.getName());
+    }
+
+    if (benchmarkMethod.getReturnType() != double.class) {
+      throw new InvalidBenchmarkException(
+          "Arbitrary measurement methods must have a return type of double: "
+              + benchmarkMethod.getName());
+    }
+
+    // Static technically doesn't hurt anything, but it's just the completely wrong idea
+    if (Util.isStatic(benchmarkMethod)) {
+      throw new InvalidBenchmarkException(
+          "Arbitrary measurement methods must not be static: " + benchmarkMethod.getName());
+    }
+
+    if (!Util.isPublic(benchmarkMethod)) {
+      throw new InvalidBenchmarkException(
+          "Arbitrary measurement methods must be public: " + benchmarkMethod.getName());
+    }
+
+    return new ArbitraryMeasurementInstrumentation(benchmarkMethod);
+  }
+
+  @Override public TrialSchedulingPolicy schedulingPolicy() {
+    // We could allow it here but in general it would depend on the particular measurement so it
+    // should probably be configured by the user.  For now we just disable it.
+    return TrialSchedulingPolicy.SERIAL;
+  }
+
+  private final class ArbitraryMeasurementInstrumentation extends Instrumentation {
+    protected ArbitraryMeasurementInstrumentation(Method benchmarkMethod) {
+      super(benchmarkMethod);
+    }
+
+    @Override
+    public void dryRun(Object benchmark) throws InvalidBenchmarkException {
+      try {
+        benchmarkMethod.invoke(benchmark);
+      } catch (IllegalAccessException impossible) {
+        throw new AssertionError(impossible);
+      } catch (InvocationTargetException e) {
+        Throwable userException = e.getCause();
+        propagateIfInstanceOf(userException, SkipThisScenarioException.class);
+        throw new UserCodeException(userException);
+      }
+    }
+
+    @Override
+    public Class<? extends Worker> workerClass() {
+      return ArbitraryMeasurementWorker.class;
+    }
+
+    @Override public ImmutableMap<String, String> workerOptions() {
+      return ImmutableMap.of(GC_BEFORE_EACH_OPTION, options.get(GC_BEFORE_EACH_OPTION));
+    }
+
+    @Override
+    MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
+      return new SingleMeasurementCollectingVisitor();
+    }
+  }
+
+  @Override
+  public ImmutableSet<String> instrumentOptions() {
+    return ImmutableSet.of(GC_BEFORE_EACH_OPTION);
+  }
+
+  private static final class SingleMeasurementCollectingVisitor extends AbstractLogMessageVisitor
+      implements MeasurementCollectingVisitor {
+    Optional<Measurement> measurement = Optional.absent();
+
+    @Override
+    public boolean isDoneCollecting() {
+      return measurement.isPresent();
+    }
+
+    @Override
+    public boolean isWarmupComplete() {
+      return true;
+    }
+
+    @Override
+    public ImmutableList<Measurement> getMeasurements() {
+      return ImmutableList.copyOf(measurement.asSet());
+    }
+
+    @Override
+    public void visit(StopMeasurementLogMessage logMessage) {
+      this.measurement = Optional.of(Iterables.getOnlyElement(logMessage.measurements()));
+    }
+
+    @Override 
+    public ImmutableList<String> getMessages() {
+      return ImmutableList.of();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/BenchmarkClass.java b/caliper/src/main/java/com/google/caliper/runner/BenchmarkClass.java
new file mode 100644
index 0000000..2aa6e1a
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/BenchmarkClass.java
@@ -0,0 +1,199 @@
+/*
+ * 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.runner;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Throwables.propagateIfInstanceOf;
+
+import com.google.caliper.AfterExperiment;
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Param;
+import com.google.caliper.api.SkipThisScenarioException;
+import com.google.caliper.api.VmOptions;
+import com.google.caliper.util.InvalidCommandException;
+import com.google.caliper.util.Reflection;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An instance of this type represents a user-provided class. It manages creating, setting up and
+ * destroying instances of that class.
+ */
+final class BenchmarkClass {
+  private static final Logger logger = Logger.getLogger(BenchmarkClass.class.getName());
+
+  static BenchmarkClass forClass(Class<?> theClass) throws InvalidBenchmarkException {
+    return new BenchmarkClass(theClass);
+  }
+
+  final Class<?> theClass;
+  private final ParameterSet userParameters;
+  private final ImmutableSet<String> benchmarkFlags;
+
+  private BenchmarkClass(Class<?> theClass) throws InvalidBenchmarkException {
+    this.theClass = checkNotNull(theClass);
+
+    if (!theClass.getSuperclass().equals(Object.class)) {
+      throw new InvalidBenchmarkException(
+          "%s must not extend any class other than %s. Prefer composition.",
+          theClass, Object.class);
+    }
+
+    if (Modifier.isAbstract(theClass.getModifiers())) {
+      throw new InvalidBenchmarkException("Class '%s' is abstract", theClass);
+    }
+
+    // TODO: check for nested, non-static classes (non-abstract, but no constructor?)
+    // this will fail later anyway (no way to declare parameterless nested constr., but
+    // maybe signal this better?
+
+    this.userParameters = ParameterSet.create(theClass, Param.class);
+
+    this.benchmarkFlags = getVmOptions(theClass);
+  }
+
+  ImmutableSet<Method> beforeExperimentMethods() {
+    return Reflection.getAnnotatedMethods(theClass, BeforeExperiment.class);
+  }
+
+  ImmutableSet<Method> afterExperimentMethods() {
+    return Reflection.getAnnotatedMethods(theClass, AfterExperiment.class);
+  }
+
+  public ParameterSet userParameters() {
+    return userParameters;
+  }
+
+  public ImmutableSet<String> vmOptions() {
+    return benchmarkFlags;
+  }
+
+  // TODO(gak): use these methods in the worker as well
+  public void setUpBenchmark(Object benchmarkInstance) throws UserCodeException {
+    boolean setupSuccess = false;
+    try {
+      callSetUp(benchmarkInstance);
+      setupSuccess = true;
+    } finally {
+      // If setUp fails, we should call tearDown. If this method throws an exception, we
+      // need to call tearDown from here, because no one else has the reference to the
+      // Benchmark.
+      if (!setupSuccess) {
+        try {
+          callTearDown(benchmarkInstance);
+        } catch (UserCodeException e) {
+          // The exception thrown during setUp shouldn't be lost, as it's probably more
+          // important to the user.
+          logger.log(
+              Level.INFO,
+              "in @AfterExperiment methods called because @BeforeExperiment methods failed",
+              e);
+        }
+      }
+    }
+  }
+
+  public void cleanup(Object benchmark) throws UserCodeException {
+    callTearDown(benchmark);
+  }
+
+  @VisibleForTesting Class<?> benchmarkClass() {
+    return theClass;
+  }
+
+  public String name() {
+    return theClass.getName();
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof BenchmarkClass) {
+      BenchmarkClass that = (BenchmarkClass) obj;
+      return this.theClass.equals(that.theClass);
+    } else {
+      return false;
+    }
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(theClass);
+  }
+
+  @Override public String toString() {
+    return name();
+  }
+
+  private void callSetUp(Object benchmark) throws UserCodeException {
+    for (Method method : beforeExperimentMethods()) {
+      try {
+        method.invoke(benchmark);
+      } catch (IllegalAccessException e) {
+        throw new AssertionError(e);
+      } catch (InvocationTargetException e) {
+        propagateIfInstanceOf(e.getCause(), SkipThisScenarioException.class);
+        throw new UserCodeException(
+            "Exception thrown from a @BeforeExperiment method", e.getCause());
+      }
+    }
+  }
+
+  private void callTearDown(Object benchmark) throws UserCodeException {
+    for (Method method : afterExperimentMethods()) {
+      try {
+        method.invoke(benchmark);
+      } catch (IllegalAccessException e) {
+        throw new AssertionError(e);
+      } catch (InvocationTargetException e) {
+        propagateIfInstanceOf(e.getCause(), SkipThisScenarioException.class);
+        throw new UserCodeException(
+            "Exception thrown from an @AfterExperiment method", e.getCause());
+      }
+    }
+  }
+
+  private static ImmutableSet<String> getVmOptions(Class<?> benchmarkClass) {
+    VmOptions annotation = benchmarkClass.getAnnotation(VmOptions.class);
+    return (annotation == null)
+        ? ImmutableSet.<String>of()
+        : ImmutableSet.copyOf(annotation.value());
+  }
+
+  void validateParameters(ImmutableSetMultimap<String, String> parameters)
+      throws InvalidCommandException {
+    for (String paramName : parameters.keySet()) {
+      Parameter parameter = userParameters.get(paramName);
+      if (parameter == null) {
+        throw new InvalidCommandException("unrecognized parameter: " + paramName);
+      }
+      try {
+        parameter.validate(parameters.get(paramName));
+      } catch (InvalidBenchmarkException e) {
+        // TODO(kevinb): this is weird.
+        throw new InvalidCommandException(e.getMessage());
+      }
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/BenchmarkClassChecker.java b/caliper/src/main/java/com/google/caliper/runner/BenchmarkClassChecker.java
new file mode 100644
index 0000000..a7c777d
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/BenchmarkClassChecker.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 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.runner;
+
+import com.google.caliper.config.ConfigModule;
+import com.google.caliper.options.OptionsModule;
+import com.google.caliper.util.OutputModule;
+import com.google.common.collect.ImmutableSet;
+
+import dagger.Component;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.util.List;
+
+import javax.inject.Singleton;
+
+/**
+ * Determines whether a class contains one or more benchmarks or not.
+ *
+ * <p>Useful for tools that need to check whether a class contains benchmarks before running them
+ * by calling an appropriate method on {@link CaliperMain}.
+ */
+// This should be considered part of the public API alongside {@link CaliperMain}.
+public final class BenchmarkClassChecker {
+
+  /**
+   * Create a new instance of {@link BenchmarkClassChecker}.
+   *
+   * @param arguments a list of command line arguments for Caliper, can include any of the
+   *     options supported by Caliper.
+   * @return a new instance of {@link BenchmarkClassChecker}.
+   */
+  public static BenchmarkClassChecker create(List<String> arguments) {
+    return new BenchmarkClassChecker(arguments);
+  }
+
+  /**
+   * The set of {@link Instrument instruments} that are used to determine whether a class has any
+   * methods suitable for benchmarking.
+   */
+  private final ImmutableSet<Instrument> instruments;
+
+  private BenchmarkClassChecker(List<String> arguments) {
+    String[] args = arguments.toArray(new String[arguments.size()]);
+    InstrumentProvider instrumentProvider = DaggerBenchmarkClassChecker_InstrumentProvider.builder()
+        .optionsModule(OptionsModule.withoutBenchmarkClass(args))
+        .outputModule(new OutputModule(new PrintWriter(System.out), new PrintWriter(System.err)))
+        .build();
+
+    instruments = instrumentProvider.instruments();
+  }
+
+  /**
+   * Check to see whether the supplied class contains at least one benchmark method that can be run
+   * by caliper.
+   *
+   * @param theClass the class that may contain one or more benchmark methods.
+   * @return true if the class does contain a benchmark method, false otherwise.
+   */
+  public boolean isBenchmark(Class<?> theClass) {
+    for (Method method : theClass.getDeclaredMethods()) {
+      for (Instrument instrument : instruments) {
+        if (instrument.isBenchmarkMethod(method)) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  @Singleton
+  @Component(modules = {
+      ConfigModule.class,
+      ExperimentingRunnerModule.class,
+      OptionsModule.class,
+      OutputModule.class,
+      PlatformModule.class,
+      RunnerModule.class,
+  })
+  /**
+   * Provides the set of supported {@link Instrument instruments}.
+   */
+  interface InstrumentProvider {
+    ImmutableSet<Instrument> instruments();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/BenchmarkClassModule.java b/caliper/src/main/java/com/google/caliper/runner/BenchmarkClassModule.java
new file mode 100644
index 0000000..35b76be
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/BenchmarkClassModule.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import com.google.caliper.runner.Running.AfterExperimentMethods;
+import com.google.caliper.runner.Running.BeforeExperimentMethods;
+import com.google.common.collect.ImmutableSet;
+import dagger.Module;
+import dagger.Provides;
+
+import java.lang.reflect.Method;
+import javax.inject.Singleton;
+
+/**
+ * Binds objects related to a benchmark class.
+ */
+// TODO(gak): move more of benchmark class into this module
+@Module
+public final class BenchmarkClassModule {
+
+  @Provides
+  @Singleton
+  static BenchmarkClass provideBenchmarkClass(@Running.BenchmarkClass Class<?> benchmarkClassObject)
+      throws InvalidBenchmarkException {
+    return BenchmarkClass.forClass(benchmarkClassObject);
+  }
+
+  @Provides
+  @BeforeExperimentMethods
+  static ImmutableSet<Method> provideBeforeExperimentMethods(
+      BenchmarkClass benchmarkClass) {
+    return benchmarkClass.beforeExperimentMethods();
+  }
+
+  @Provides
+  @AfterExperimentMethods
+  static ImmutableSet<Method> provideAfterExperimentMethods(
+      BenchmarkClass benchmarkClass) {
+    return benchmarkClass.afterExperimentMethods();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/BenchmarkCreator.java b/caliper/src/main/java/com/google/caliper/runner/BenchmarkCreator.java
new file mode 100644
index 0000000..548d272
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/BenchmarkCreator.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 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.runner;
+
+import com.google.caliper.Param;
+import com.google.caliper.util.Parser;
+import com.google.caliper.util.Parsers;
+import com.google.common.collect.ImmutableSortedMap;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.ParseException;
+
+import javax.inject.Inject;
+
+/**
+ * Responsible for creating instances of the benchmark class.
+ */
+final class BenchmarkCreator {
+
+  private static final String BENCHMARK_NO_PUBLIC_DEFAULT_CONSTRUCTOR =
+      "Benchmark %s does not have a publicly visible default constructor";
+
+  private final Class<?> benchmarkClass;
+  private final ImmutableSortedMap<String, String> parameters;
+  private final Constructor<?> benchmarkClassCtor;
+
+  @Inject
+  BenchmarkCreator(
+      @Running.BenchmarkClass Class<?> benchmarkClass,
+      @Running.Benchmark ImmutableSortedMap<String, String> parameters) {
+    this.benchmarkClass = benchmarkClass;
+    this.benchmarkClassCtor = findDefaultConstructor(benchmarkClass);
+    this.parameters = parameters;
+  }
+
+  private static Constructor<?> findDefaultConstructor(Class<?> benchmarkClass) {
+    Constructor<?> defaultConstructor = null;
+    for (Constructor<?> constructor : benchmarkClass.getDeclaredConstructors()) {
+      if (constructor.getParameterTypes().length == 0) {
+        defaultConstructor = constructor;
+        defaultConstructor.setAccessible(true);
+        break;
+      }
+    }
+    if (defaultConstructor == null) {
+      throw new UserCodeException(
+          String.format(BENCHMARK_NO_PUBLIC_DEFAULT_CONSTRUCTOR, benchmarkClass), null);
+    }
+    return defaultConstructor;
+  }
+
+  Object createBenchmarkInstance() {
+    Object instance;
+    try {
+      instance = benchmarkClassCtor.newInstance();
+    } catch (InstantiationException e) {
+      throw new AssertionError(e);
+    } catch (IllegalAccessException e) {
+      throw new AssertionError(e);
+    } catch (InvocationTargetException e) {
+      Throwable userException = e.getCause();
+      throw new UserCodeException(userException);
+    }
+
+    // Inject values for the user parameters.
+    for (Field field : benchmarkClass.getDeclaredFields()) {
+      if (field.isAnnotationPresent(Param.class)) {
+        try {
+          field.setAccessible(true);
+          Parser<?> parser = Parsers.conventionalParser(field.getType());
+          field.set(instance, parser.parse(parameters.get(field.getName())));
+        } catch (NoSuchMethodException e) {
+          throw new RuntimeException(e);
+        } catch (ParseException e) {
+          throw new RuntimeException(e);
+        } catch (IllegalArgumentException e) {
+          throw new AssertionError("types have been checked");
+        } catch (IllegalAccessException e) {
+          throw new AssertionError("already set access");
+        }
+      }
+    }
+
+    return instance;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/BenchmarkMethods.java b/caliper/src/main/java/com/google/caliper/runner/BenchmarkMethods.java
new file mode 100644
index 0000000..f109b50
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/BenchmarkMethods.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.caliper.Benchmark;
+import com.google.caliper.util.Util;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * Utilities for working with methods annotated by {@link Benchmark}.
+ */
+final class BenchmarkMethods {
+  private static final Class<?>[] MACROBENCHMARK_PARAMS = new Class<?>[] {};
+  private static final Class<?>[] MICROBENCHMARK_PARAMS = new Class<?>[] {int.class};
+  private static final Class<?>[] PICOBENCHMARK_PARAMS = new Class<?>[] {long.class};
+
+  private BenchmarkMethods() {}
+
+  enum Type {
+    MACRO,
+    MICRO,
+    PICO;
+
+    static Type of(Method benchmarkMethod) {
+      Class<?>[] parameterTypes = benchmarkMethod.getParameterTypes();
+      if (Arrays.equals(parameterTypes, MACROBENCHMARK_PARAMS)) {
+        return MACRO;
+      } else if (Arrays.equals(parameterTypes, MICROBENCHMARK_PARAMS)) {
+        return MICRO;
+      } else if (Arrays.equals(parameterTypes, PICOBENCHMARK_PARAMS)) {
+        return PICO;
+      } else {
+        throw new IllegalArgumentException("invalid method parameters: " + benchmarkMethod);
+      }
+    }
+  }
+
+  /**
+   * Several instruments look for benchmark methods like {@code timeBlah(int reps)}; this is the
+   * centralized code that identifies such methods.
+   *
+   * <p>This method does not check the correctness of the argument types.
+   */
+  static boolean isTimeMethod(Method method) {
+    return method.getName().startsWith("time") && Util.isPublic(method);
+  }
+
+  /**
+   * For instruments that use {@link #isTimeMethod} to identify their methods, this method checks
+   * the {@link Method} appropriately.
+   */
+  static Method checkTimeMethod(Method timeMethod) throws InvalidBenchmarkException {
+    checkArgument(isTimeMethod(timeMethod));
+    Class<?>[] parameterTypes = timeMethod.getParameterTypes();
+    if (!Arrays.equals(parameterTypes, new Class<?>[] {int.class})
+        && !Arrays.equals(parameterTypes, new Class<?>[] {long.class})) {
+      throw new InvalidBenchmarkException(
+          "Microbenchmark methods must accept a single int parameter: " + timeMethod.getName());
+    }
+
+    // Static technically doesn't hurt anything, but it's just the completely wrong idea
+    if (Util.isStatic(timeMethod)) {
+      throw new InvalidBenchmarkException(
+          "Microbenchmark methods must not be static: " + timeMethod.getName());
+    }
+    return timeMethod;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/BenchmarkParameters.java b/caliper/src/main/java/com/google/caliper/runner/BenchmarkParameters.java
new file mode 100644
index 0000000..679c2bd
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/BenchmarkParameters.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 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.runner;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/** Binding annotation for the parameters applied to a benchmark. */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+@interface BenchmarkParameters {}
diff --git a/caliper/src/main/java/com/google/caliper/runner/CaliperMain.java b/caliper/src/main/java/com/google/caliper/runner/CaliperMain.java
new file mode 100644
index 0000000..c81e407
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/CaliperMain.java
@@ -0,0 +1,138 @@
+/*
+ * 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.runner;
+
+import static com.google.common.collect.ObjectArrays.concat;
+
+import com.google.caliper.config.InvalidConfigurationException;
+import com.google.caliper.options.CaliperOptions;
+import com.google.caliper.options.OptionsModule;
+import com.google.caliper.util.InvalidCommandException;
+import com.google.caliper.util.OutputModule;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.util.concurrent.ServiceManager;
+
+import java.io.PrintWriter;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.Nullable;
+
+/**
+ * Primary entry point for the caliper benchmark runner application; run with {@code --help} for
+ * details. This class's only purpose is to take care of anything that's specific to command-line
+ * invocation and then hand off to {@code CaliperRun}. That is, a hypothetical GUI benchmark runner
+ * might still use {@code CaliperRun} but would skip using this class.
+ */
+public final class CaliperMain {
+  /**
+   * Your benchmark classes can implement main() like this: <pre>   {@code
+   *
+   *   public static void main(String[] args) {
+   *     CaliperMain.main(MyBenchmark.class, args);
+   *   }}</pre>
+   *
+   * Note that this method does invoke {@link System#exit} when it finishes. Consider {@link
+   * #exitlessMain} if you don't want that.
+   *
+   * <p>Measurement is handled in a subprocess, so it will not use {@code benchmarkClass} itself;
+   * the class is provided here only as a shortcut for specifying the full class <i>name</i>. The
+   * class that gets loaded later could be completely different.
+   */
+  public static void main(Class<?> benchmarkClass, String[] args) {
+    main(concat(args, benchmarkClass.getName()));
+  }
+
+  /**
+   * Entry point for the caliper benchmark runner application; run with {@code --help} for details.
+   */
+  public static void main(String[] args) {
+    PrintWriter stdout = new PrintWriter(System.out, true);
+    PrintWriter stderr = new PrintWriter(System.err, true);
+    int code = 1; // pessimism!
+
+    try {
+      exitlessMain(args, stdout, stderr);
+      code = 0;
+
+    } catch (InvalidCommandException e) {
+      e.display(stderr);
+      code = e.exitCode();
+
+    } catch (InvalidBenchmarkException e) {
+      e.display(stderr);
+
+    } catch (InvalidConfigurationException e) {
+      e.display(stderr);
+
+    } catch (Throwable t) {
+      t.printStackTrace(stderr);
+      stdout.println();
+      stdout.println("An unexpected exception has been thrown by the caliper runner.");
+      stdout.println("Please see https://sites.google.com/site/caliperusers/issues");
+    }
+
+    stdout.flush();
+    stderr.flush();
+    System.exit(code);
+  }
+
+  private static final String LEGACY_ENV = "USE_LEGACY_CALIPER";
+
+  public static void exitlessMain(String[] args, PrintWriter stdout, PrintWriter stderr)
+      throws InvalidCommandException, InvalidBenchmarkException, InvalidConfigurationException {
+    @Nullable String legacyCaliperEnv = System.getenv(LEGACY_ENV);
+    if (!Strings.isNullOrEmpty(legacyCaliperEnv)) {
+      System.err.println("Legacy Caliper is no more. " + LEGACY_ENV + " has no effect.");
+    }
+    try {
+      MainComponent mainComponent = DaggerMainComponent.builder()
+          .optionsModule(OptionsModule.withBenchmarkClass(args))
+          .outputModule(new OutputModule(stdout, stderr))
+          .build();
+      CaliperOptions options = mainComponent.getCaliperOptions();
+      if (options.printConfiguration()) {
+        stdout.println("Configuration:");
+        ImmutableSortedMap<String, String> sortedProperties =
+            ImmutableSortedMap.copyOf(mainComponent.getCaliperConfig().properties());
+        for (Entry<String, String> entry : sortedProperties.entrySet()) {
+          stdout.printf("  %s = %s%n", entry.getKey(), entry.getValue());
+        }
+      }
+      // check that the parameters are valid
+      mainComponent.getBenchmarkClass().validateParameters(options.userParameters());
+      ServiceManager serviceManager = mainComponent.getServiceManager();
+      serviceManager.startAsync().awaitHealthy();
+      try {
+        CaliperRun run = mainComponent.getCaliperRun();
+        run.run(); // throws IBE
+      } finally {
+        try {
+          // We have some shutdown logic to ensure that files are cleaned up so give it a chance to
+          // run
+          serviceManager.stopAsync().awaitStopped(10, TimeUnit.SECONDS);
+        } catch (TimeoutException e) {
+          // That's fine
+        }
+      }
+    } finally {
+      // courtesy flush
+      stderr.flush();
+      stdout.flush();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/MeasurementType.java b/caliper/src/main/java/com/google/caliper/runner/CaliperRun.java
similarity index 74%
rename from caliper/src/main/java/com/google/caliper/MeasurementType.java
rename to caliper/src/main/java/com/google/caliper/runner/CaliperRun.java
index 30de69f..2555016 100644
--- a/caliper/src/main/java/com/google/caliper/MeasurementType.java
+++ b/caliper/src/main/java/com/google/caliper/runner/CaliperRun.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2012 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
+package com.google.caliper.runner;
 
-import com.google.common.annotations.GwtCompatible;
-
-@GwtCompatible
-public enum MeasurementType {
-  TIME, INSTANCE, MEMORY, DEBUG
+/**
+ * A single invocation of caliper.
+ */
+public interface CaliperRun {
+  void run() throws InvalidBenchmarkException;
 }
diff --git a/caliper/src/main/java/com/google/caliper/runner/CommonInstrumentOptions.java b/caliper/src/main/java/com/google/caliper/runner/CommonInstrumentOptions.java
new file mode 100644
index 0000000..4998601
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/CommonInstrumentOptions.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 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.runner;
+
+/**
+ * Instrument option identifiers shared between instruments.
+ */
+final class CommonInstrumentOptions {
+  private CommonInstrumentOptions() {}
+
+  static final String MEASUREMENTS_OPTION = "measurements";
+  static final String GC_BEFORE_EACH_OPTION = "gcBeforeEach";
+  static final String WARMUP_OPTION = "warmup";
+  static final String MAX_WARMUP_WALL_TIME_OPTION = "maxWarmupWallTime";
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ConsoleOutput.java b/caliper/src/main/java/com/google/caliper/runner/ConsoleOutput.java
new file mode 100644
index 0000000..a308c58
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ConsoleOutput.java
@@ -0,0 +1,149 @@
+/**
+ * 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.runner;
+
+import com.google.caliper.model.BenchmarkSpec;
+import com.google.caliper.model.InstrumentSpec;
+import com.google.caliper.model.Measurement;
+import com.google.caliper.model.Scenario;
+import com.google.caliper.model.Trial;
+import com.google.caliper.model.VmSpec;
+import com.google.caliper.util.Stdout;
+import com.google.common.base.Function;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.Sets;
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math.stat.descriptive.rank.Percentile;
+
+import java.io.Closeable;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Prints a brief summary of the results collected.  It does not contain the measurements themselves
+ * as that is the responsibility of the webapp.
+ */
+final class ConsoleOutput implements Closeable {
+  private final PrintWriter stdout;
+
+  private final Set<InstrumentSpec> instrumentSpecs = Sets.newHashSet();
+  private final Set<VmSpec> vmSpecs = Sets.newHashSet();
+  private final Set<BenchmarkSpec> benchmarkSpecs = Sets.newHashSet();
+  private int numMeasurements = 0;
+  private int trialsCompleted = 0;
+  private final int numberOfTrials;
+  private final Stopwatch stopwatch;
+
+
+  ConsoleOutput(@Stdout PrintWriter stdout, int numberOfTrials, Stopwatch stopwatch) {
+    this.stdout = stdout;
+    this.numberOfTrials = numberOfTrials;
+    this.stopwatch = stopwatch;
+  }
+
+  /**
+   * Prints a short message when we observe a trial failure.
+   */  
+  void processFailedTrial(TrialFailureException e) {
+    trialsCompleted++;
+    // TODO(lukes): it would be nice to print which trial failed.  Consider adding Experiment data
+    // to the TrialFailureException.
+    stdout.println(
+        "ERROR: Trial failed to complete (its results will not be included in the run):\n"
+            + "  " + e.getMessage());
+    stdout.flush();
+  }
+  
+  /**
+   * Prints a summary of a successful trial result.
+   */
+  void processTrial(TrialResult result) {
+    trialsCompleted++;
+    stdout.printf("Trial Report (%d of %d):%n  Experiment %s%n", 
+        trialsCompleted, numberOfTrials, result.getExperiment());
+    if (!result.getTrialMessages().isEmpty()) {
+      stdout.println("  Messages:");
+      for (String message : result.getTrialMessages()) {
+        stdout.print("    ");
+        stdout.println(message);
+      }
+    }
+    Trial trial = result.getTrial();
+    ImmutableListMultimap<String, Measurement> measurementsIndex =
+        new ImmutableListMultimap.Builder<String, Measurement>()
+            .orderKeysBy(Ordering.natural())
+            .putAll(Multimaps.index(trial.measurements(), new Function<Measurement, String>() {
+              @Override public String apply(Measurement input) {
+                return input.description();
+              }
+            }))
+            .build();
+    stdout.println("  Results:");
+    for (Entry<String, Collection<Measurement>> entry : measurementsIndex.asMap().entrySet()) {
+      Collection<Measurement> measurements = entry.getValue();
+      ImmutableSet<String> units = FluentIterable.from(measurements)
+          .transform(new Function<Measurement, String>() {
+            @Override public String apply(Measurement input) {
+              return input.value().unit();
+            }
+          }).toSet();
+      double[] weightedValues = new double[measurements.size()];
+      int i = 0;
+      for (Measurement measurement : measurements) {
+        weightedValues[i] = measurement.value().magnitude() / measurement.weight();
+        i++;
+      }
+      Percentile percentile = new Percentile();
+      percentile.setData(weightedValues);
+      DescriptiveStatistics descriptiveStatistics = new DescriptiveStatistics(weightedValues);
+      String unit = Iterables.getOnlyElement(units);
+      stdout.printf(
+          "    %s%s: min=%.2f, 1st qu.=%.2f, median=%.2f, mean=%.2f, 3rd qu.=%.2f, max=%.2f%n",
+          entry.getKey(), unit.isEmpty() ? "" : "(" + unit + ")",
+          descriptiveStatistics.getMin(), percentile.evaluate(25),
+          percentile.evaluate(50), descriptiveStatistics.getMean(),
+          percentile.evaluate(75), descriptiveStatistics.getMax());
+    }
+    
+    instrumentSpecs.add(trial.instrumentSpec());
+    Scenario scenario = trial.scenario();
+    vmSpecs.add(scenario.vmSpec());
+    benchmarkSpecs.add(scenario.benchmarkSpec());
+    numMeasurements += trial.measurements().size();
+  }
+
+  @Override public void close() {
+    if (trialsCompleted == numberOfTrials) {  // if we finished all the trials
+      stdout.printf("Collected %d measurements from:%n", numMeasurements);
+      stdout.printf("  %d instrument(s)%n", instrumentSpecs.size());
+      stdout.printf("  %d virtual machine(s)%n", vmSpecs.size());
+      stdout.printf("  %d benchmark(s)%n", benchmarkSpecs.size());
+      stdout.println();
+      stdout.format("Execution complete: %s.%n", stopwatch.stop());
+      stdout.flush();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/EnvironmentGetter.java b/caliper/src/main/java/com/google/caliper/runner/EnvironmentGetter.java
new file mode 100644
index 0000000..798e724
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/EnvironmentGetter.java
@@ -0,0 +1,119 @@
+/*
+ * 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.runner;
+
+import com.google.caliper.model.Host;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * An instance of this class is responsible for returning a Map that describes the environment:
+ * JVM version, os details, etc.
+ */
+final class EnvironmentGetter {
+  Host getHost() {
+    return new Host.Builder()
+        .addAllProperies(getProperties())
+        .build();
+  }
+  
+  private Map<String, String> getProperties() {
+    TreeMap<String, String> propertyMap = Maps.newTreeMap();
+
+    Map<String, String> sysProps = Maps.fromProperties(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("host.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"));
+
+    if (osName.equals("Linux")) {
+      getLinuxEnvironment(propertyMap);
+    }
+
+    return 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());
+  }
+
+  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 file.
+   * Unlike standard Java properties files, {@code reader} is allowed to list
+   * the same property multiple times. Comments etc. are unsupported.
+   *
+   * <p>If there's any problem reading the file's contents, we'll return an
+   * empty Multimap.
+   */
+  private static Multimap<String, String> propertiesFromLinuxFile(String file) {
+    try {
+      List<String> lines = Files.readLines(new File(file), Charset.defaultCharset());
+      ImmutableMultimap.Builder<String, String> result = ImmutableMultimap.builder();
+      for (String line : lines) {
+        // TODO(schmoe): replace with Splitter (in Guava release 10)
+        String[] parts = line.split("\\s*\\:\\s*", 2);
+        if (parts.length == 2) {
+          result.put(parts[0], parts[1]);
+        }
+      }
+      return result.build();
+    } catch (IOException e) {
+      // If there's any problem reading the file, just return an empty multimap.
+      return ImmutableMultimap.of();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/Experiment.java b/caliper/src/main/java/com/google/caliper/runner/Experiment.java
new file mode 100644
index 0000000..f0a877f
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/Experiment.java
@@ -0,0 +1,82 @@
+/*
+ * 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.runner;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.caliper.runner.Instrument.Instrumentation;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSortedMap;
+
+import java.util.Map;
+
+/**
+ * A single "premise" for making benchmark measurements: which class and method to invoke, which VM
+ * to use, which choices for user parameters and vmArguments to fill in and which instrument to use
+ * to measure. A caliper run will compute all possible scenarios using
+ * {@link FullCartesianExperimentSelector}, and will run one or more trials of each.
+ */
+final class Experiment {
+  private final Instrumentation instrumentation;
+  private final VirtualMachine vm;
+  private final ImmutableSortedMap<String, String> userParameters;
+
+  Experiment(
+      Instrumentation instrumentation,
+      Map<String, String> userParameters,
+      VirtualMachine vm) {
+    this.instrumentation = checkNotNull(instrumentation);
+    this.userParameters = ImmutableSortedMap.copyOf(userParameters);
+    this.vm = checkNotNull(vm);
+  }
+
+  Instrumentation instrumentation() {
+    return instrumentation;
+  }
+
+  ImmutableSortedMap<String, String> userParameters() {
+    return userParameters;
+  }
+
+  VirtualMachine vm() {
+    return vm;
+  }
+
+  @Override public boolean equals(Object object) {
+    if (object instanceof Experiment) {
+      Experiment that = (Experiment) object;
+      return this.instrumentation.equals(that.instrumentation)
+          && this.vm.equals(that.vm)
+          && this.userParameters.equals(that.userParameters);
+    }
+    return false;
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(instrumentation, vm, userParameters);
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper("")
+        .add("instrument", instrumentation.instrument())
+        .add("benchmarkMethod", instrumentation.benchmarkMethod.getName())
+        .add("vm", vm.name)
+        .add("parameters", userParameters)
+        .toString();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/UploadResults.java b/caliper/src/main/java/com/google/caliper/runner/ExperimentComponent.java
similarity index 61%
copy from caliper/src/main/java/com/google/caliper/UploadResults.java
copy to caliper/src/main/java/com/google/caliper/runner/ExperimentComponent.java
index 7dae7af..174c5f5 100644
--- a/caliper/src/main/java/com/google/caliper/UploadResults.java
+++ b/caliper/src/main/java/com/google/caliper/runner/ExperimentComponent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2015 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
+package com.google.caliper.runner;
 
-import java.io.File;
+import static com.google.caliper.runner.Running.Benchmark;
+
+import dagger.Subcomponent;
 
 /**
- * Usage: UploadResults <file_or_dir>
+ * Provides access to the benchmark instance to use for the experiment.
  */
-public class UploadResults {
-  public static void main(String[] args) {
-    new Runner().uploadResultsFileOrDir(new File(args[0]));
-  }
+@Subcomponent(modules = ExperimentModule.class)
+public interface ExperimentComponent {
+  @Benchmark Object getBenchmarkInstance();
 }
diff --git a/caliper/src/main/java/com/google/caliper/runner/ExperimentModule.java b/caliper/src/main/java/com/google/caliper/runner/ExperimentModule.java
new file mode 100644
index 0000000..79711a1
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ExperimentModule.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static com.google.caliper.runner.Running.Benchmark;
+import static com.google.caliper.runner.Running.BenchmarkMethod;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.caliper.bridge.WorkerSpec;
+import com.google.caliper.util.Util;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedMap;
+import dagger.Module;
+import dagger.Provides;
+
+import java.lang.reflect.Method;
+
+/**
+ * A module that binds data specific to a single experiment.
+ */
+@Module
+public final class ExperimentModule {
+  private final ImmutableSortedMap<String, String> parameters;
+  private final Method benchmarkMethod;
+
+  private ExperimentModule(
+      Method benchmarkMethod,
+      ImmutableSortedMap<String, String> parameters) {
+    this.parameters = checkNotNull(parameters);
+    this.benchmarkMethod = checkNotNull(benchmarkMethod);
+  }
+
+  public static ExperimentModule forExperiment(Experiment experiment) {
+    Method benchmarkMethod = experiment.instrumentation().benchmarkMethod();
+    return new ExperimentModule(
+        benchmarkMethod,
+        experiment.userParameters());
+  }
+
+  public static ExperimentModule forWorkerSpec(WorkerSpec spec)
+      throws ClassNotFoundException {
+    Class<?> benchmarkClass = Util.loadClass(spec.benchmarkSpec.className());
+    Method benchmarkMethod = findBenchmarkMethod(benchmarkClass, spec.benchmarkSpec.methodName(),
+        spec.methodParameterClasses);
+    benchmarkMethod.setAccessible(true);
+    return new ExperimentModule(benchmarkMethod, spec.benchmarkSpec.parameters());
+  }
+
+  @Provides
+  @Benchmark
+  static Object provideRunningBenchmark(BenchmarkCreator creator) {
+    return creator.createBenchmarkInstance();
+  }
+
+  @Provides
+  @BenchmarkMethod
+  String provideRunningBenchmarkMethodName() {
+    return benchmarkMethod.getName();
+  }
+
+  @Provides
+  @BenchmarkMethod
+  Method provideRunningBenchmarkMethod() {
+    return benchmarkMethod;
+  }
+
+  @Provides
+  @Benchmark
+  ImmutableSortedMap<String, String> provideUserParameters() {
+    return parameters;
+  }
+
+  private static Method findBenchmarkMethod(Class<?> benchmark, String methodName,
+      ImmutableList<Class<?>> methodParameterClasses) {
+    Class<?>[] params = methodParameterClasses.toArray(new Class<?>[methodParameterClasses.size()]);
+    try {
+      return benchmark.getDeclaredMethod(methodName, params);
+    } catch (NoSuchMethodException e) {
+      throw new RuntimeException(e);
+    } catch (SecurityException e) {
+      // assertion error?
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ExperimentSelector.java b/caliper/src/main/java/com/google/caliper/runner/ExperimentSelector.java
new file mode 100644
index 0000000..fe32057
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ExperimentSelector.java
@@ -0,0 +1,31 @@
+/*
+ * 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.runner;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+
+public interface ExperimentSelector {
+  ImmutableSet<Instrument> instruments();
+  ImmutableSet<VirtualMachine> vms();
+  ImmutableSetMultimap<String, String> userParameters();
+
+  // The important method
+  ImmutableSet<Experiment> selectExperiments();
+
+  String selectionType();
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ExperimentingCaliperRun.java b/caliper/src/main/java/com/google/caliper/runner/ExperimentingCaliperRun.java
new file mode 100644
index 0000000..5214193
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ExperimentingCaliperRun.java
@@ -0,0 +1,307 @@
+/*
+ * 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.runner;
+
+import static java.util.logging.Level.WARNING;
+
+import com.google.caliper.api.ResultProcessor;
+import com.google.caliper.api.SkipThisScenarioException;
+import com.google.caliper.options.CaliperOptions;
+import com.google.caliper.util.Stdout;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Throwables;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Queues;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.FutureFallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.common.util.concurrent.Uninterruptibles;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+/**
+ * An execution of each {@link Experiment} for the configured number of trials.
+ */
+@VisibleForTesting
+public final class ExperimentingCaliperRun implements CaliperRun {
+
+  private static final Logger logger = Logger.getLogger(ExperimentingCaliperRun.class.getName());
+
+  private static final FutureFallback<Object> FALLBACK_TO_NULL = new FutureFallback<Object>() {
+    final ListenableFuture<Object> nullFuture = Futures.immediateFuture(null);
+    @Override public ListenableFuture<Object> create(Throwable t) throws Exception {
+      return nullFuture;
+    }
+  };
+
+  private final MainComponent mainComponent;
+  private final CaliperOptions options;
+  private final PrintWriter stdout;
+  private final BenchmarkClass benchmarkClass;
+  private final ImmutableSet<Instrument> instruments;
+  private final ImmutableSet<ResultProcessor> resultProcessors;
+  private final ExperimentSelector selector;
+  private final Provider<ListeningExecutorService> executorProvider;
+
+  @Inject @VisibleForTesting
+  public ExperimentingCaliperRun(
+      MainComponent mainComponent,
+      CaliperOptions options,
+      @Stdout PrintWriter stdout,
+      BenchmarkClass benchmarkClass,
+      ImmutableSet<Instrument> instruments,
+      ImmutableSet<ResultProcessor> resultProcessors,
+      ExperimentSelector selector,
+      Provider<ListeningExecutorService> executorProvider) {
+    this.mainComponent = mainComponent;
+    this.options = options;
+    this.stdout = stdout;
+    this.benchmarkClass = benchmarkClass;
+    this.instruments = instruments;
+    this.resultProcessors = resultProcessors;
+    this.selector = selector;
+    this.executorProvider = executorProvider;
+  }
+
+  @Override
+  public void run() throws InvalidBenchmarkException {
+    ImmutableSet<Experiment> allExperiments = selector.selectExperiments();
+    // TODO(lukes): move this standard-out handling into the ConsoleOutput class?
+    stdout.println("Experiment selection: ");
+    stdout.println("  Benchmark Methods:   " + FluentIterable.from(allExperiments)
+        .transform(new Function<Experiment, String>() {
+          @Override public String apply(Experiment experiment) {
+            return experiment.instrumentation().benchmarkMethod().getName();
+          }
+        }).toSet());
+    stdout.println("  Instruments:   " + FluentIterable.from(selector.instruments())
+        .transform(new Function<Instrument, String>() {
+              @Override public String apply(Instrument instrument) {
+                return instrument.name();
+              }
+            }));
+    stdout.println("  User parameters:   " + selector.userParameters());
+    stdout.println("  Virtual machines:  " + FluentIterable.from(selector.vms())
+        .transform(
+            new Function<VirtualMachine, String>() {
+              @Override public String apply(VirtualMachine vm) {
+                return vm.name;
+              }
+            }));
+    stdout.println("  Selection type:    " + selector.selectionType());
+    stdout.println();
+
+    if (allExperiments.isEmpty()) {
+      throw new InvalidBenchmarkException(
+          "There were no experiments to be performed for the class %s using the instruments %s",
+          benchmarkClass.benchmarkClass().getSimpleName(), instruments);
+    }
+
+    stdout.format("This selection yields %s experiments.%n", allExperiments.size());
+    stdout.flush();
+
+    // always dry run first.
+    ImmutableSet<Experiment> experimentsToRun = dryRun(allExperiments);
+    if (experimentsToRun.size() != allExperiments.size()) {
+      stdout.format("%d experiments were skipped.%n",
+          allExperiments.size() - experimentsToRun.size());
+    }
+
+    if (experimentsToRun.isEmpty()) {
+      throw new InvalidBenchmarkException("All experiments were skipped.");
+    }
+
+    if (options.dryRun()) {
+      return;
+    }
+
+    stdout.flush();
+
+    int totalTrials = experimentsToRun.size() * options.trialsPerScenario();
+    Stopwatch stopwatch = Stopwatch.createStarted();
+    List<ScheduledTrial> trials = createScheduledTrials(experimentsToRun, totalTrials);
+
+    final ListeningExecutorService executor = executorProvider.get();
+    List<ListenableFuture<TrialResult>> pendingTrials = scheduleTrials(trials, executor);
+    ConsoleOutput output = new ConsoleOutput(stdout, totalTrials, stopwatch);
+    try {
+      // Process results as they complete.
+      for (ListenableFuture<TrialResult> trialFuture : inCompletionOrder(pendingTrials)) {
+        try {
+          TrialResult result = trialFuture.get();
+          output.processTrial(result);
+          for (ResultProcessor resultProcessor : resultProcessors) {
+            resultProcessor.processTrial(result.getTrial());
+          }
+        } catch (ExecutionException e) {
+          if (e.getCause() instanceof TrialFailureException) {
+            output.processFailedTrial((TrialFailureException) e.getCause());
+          } else {
+            for (ListenableFuture<?> toCancel : pendingTrials) {
+              toCancel.cancel(true);
+            }
+            throw Throwables.propagate(e.getCause());
+          }
+        } catch (InterruptedException e) {
+          // be responsive to interruption, cancel outstanding work and exit
+          for (ListenableFuture<?> toCancel : pendingTrials) {
+            // N.B. TrialRunLoop is responsive to interruption.
+            toCancel.cancel(true);
+          }
+          throw new RuntimeException(e);
+        }
+      }
+    } finally {
+      executor.shutdown();
+      output.close();
+    }
+
+    for (ResultProcessor resultProcessor : resultProcessors) {
+      try {
+        resultProcessor.close();
+      } catch (IOException e) {
+        logger.log(WARNING, "Could not close a result processor: " + resultProcessor, e);
+      }
+    }
+  }
+
+  /**
+   * Schedule all the trials.
+   *
+   * <p>This method arranges all the {@link ScheduledTrial trials} to run according to their
+   * scheduling criteria.  The executor instance is responsible for enforcing max parallelism.
+   */
+  private List<ListenableFuture<TrialResult>> scheduleTrials(List<ScheduledTrial> trials,
+      final ListeningExecutorService executor) {
+    List<ListenableFuture<TrialResult>> pendingTrials = Lists.newArrayList();
+    List<ScheduledTrial> serialTrials = Lists.newArrayList();
+    for (final ScheduledTrial scheduledTrial : trials) {
+      if (scheduledTrial.policy() == TrialSchedulingPolicy.PARALLEL) {
+        pendingTrials.add(executor.submit(scheduledTrial.trialTask()));
+      } else {
+        serialTrials.add(scheduledTrial);
+      }
+    }
+    // A future representing the completion of all prior tasks. Futures.successfulAsList allows us
+    // to ignore failure.
+    ListenableFuture<?> previous = Futures.successfulAsList(pendingTrials);
+    for (final ScheduledTrial scheduledTrial : serialTrials) {
+      // each of these trials can only start after all prior trials have finished, so we use
+      // Futures.transform to force the sequencing.
+      ListenableFuture<TrialResult> current =
+          Futures.transform(
+              previous,
+              new AsyncFunction<Object, TrialResult>() {
+                @Override public ListenableFuture<TrialResult> apply(Object ignored) {
+                  return executor.submit(scheduledTrial.trialTask());
+                }
+              });
+      pendingTrials.add(current);
+      // ignore failure of the prior task.
+      previous = Futures.withFallback(current, FALLBACK_TO_NULL);
+    }
+    return pendingTrials;
+  }
+
+  /** Returns all the ScheduledTrials for this run. */
+  private List<ScheduledTrial> createScheduledTrials(ImmutableSet<Experiment> experimentsToRun,
+      int totalTrials) {
+    List<ScheduledTrial> trials = Lists.newArrayListWithCapacity(totalTrials);
+    /** This is 1-indexed because it's only used for display to users.  E.g. "Trial 1 of 27" */
+    int trialNumber = 1;
+    for (int i = 0; i < options.trialsPerScenario(); i++) {
+      for (Experiment experiment : experimentsToRun) {
+        try {
+          TrialScopeComponent trialScopeComponent = mainComponent.newTrialComponent(
+              new TrialModule(UUID.randomUUID(), trialNumber, experiment));
+
+          trials.add(trialScopeComponent.getScheduledTrial());
+        } finally {
+          trialNumber++;
+        }
+      }
+    }
+    return trials;
+  }
+
+  /**
+   * Attempts to run each given scenario once, in the current VM. Returns a set of all of the
+   * scenarios that didn't throw a {@link SkipThisScenarioException}.
+   */
+  ImmutableSet<Experiment> dryRun(Iterable<Experiment> experiments)
+      throws InvalidBenchmarkException {
+    ImmutableSet.Builder<Experiment> builder = ImmutableSet.builder();
+    for (Experiment experiment : experiments) {
+      try {
+        ExperimentComponent experimentComponent =
+            mainComponent.newExperimentComponent(ExperimentModule.forExperiment(experiment));
+        Object benchmark = experimentComponent.getBenchmarkInstance();
+        benchmarkClass.setUpBenchmark(benchmark);
+        try {
+          experiment.instrumentation().dryRun(benchmark);
+          builder.add(experiment);
+        } finally {
+          // discard 'benchmark' now; the worker will have to instantiate its own anyway
+          benchmarkClass.cleanup(benchmark);
+        }
+      } catch (SkipThisScenarioException innocuous) {}
+    }
+    return builder.build();
+  }
+
+  public static <T> ImmutableList<ListenableFuture<T>> inCompletionOrder(
+      Iterable<? extends ListenableFuture<? extends T>> futures) {
+    final ConcurrentLinkedQueue<SettableFuture<T>> delegates = Queues.newConcurrentLinkedQueue();
+    ImmutableList.Builder<ListenableFuture<T>> listBuilder = ImmutableList.builder();
+    for (final ListenableFuture<? extends T> future : futures) {
+      SettableFuture<T> delegate = SettableFuture.create();
+      // Must make sure to add the delegate to the queue first in case the future is already done
+      delegates.add(delegate);
+      future.addListener(new Runnable() {
+        @Override public void run() {
+          SettableFuture<T> delegate = delegates.remove();
+          try {
+            delegate.set(Uninterruptibles.getUninterruptibly(future));
+          } catch (ExecutionException e) {
+            delegate.setException(e.getCause());
+          } catch (CancellationException e) {
+            delegate.cancel(true);
+          }
+        }
+      }, MoreExecutors.directExecutor());
+      listBuilder.add(delegate);
+    }
+    return listBuilder.build();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ExperimentingRunnerModule.java b/caliper/src/main/java/com/google/caliper/runner/ExperimentingRunnerModule.java
new file mode 100644
index 0000000..7abcbd9
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ExperimentingRunnerModule.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2012 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.runner;
+
+import com.google.caliper.api.ResultProcessor;
+import com.google.caliper.config.CaliperConfig;
+import com.google.caliper.config.InstrumentConfig;
+import com.google.caliper.model.Host;
+import com.google.caliper.options.CaliperOptions;
+import com.google.caliper.platform.Platform;
+import com.google.caliper.runner.Instrument.Instrumentation;
+import com.google.caliper.util.InvalidCommandException;
+import com.google.caliper.util.ShortDuration;
+import com.google.caliper.util.Stderr;
+import com.google.caliper.util.Util;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Ordering;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.Service;
+
+import dagger.MapKey;
+import dagger.Module;
+import dagger.Provides;
+import dagger.Provides.Type;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+/**
+ * Configures a {@link CaliperRun} that performs experiments.
+ */
+@Module
+final class ExperimentingRunnerModule {
+  private static final String RUNNER_MAX_PARALLELISM_OPTION = "runner.maxParallelism";
+
+  @Provides(type = Type.SET)
+  static Service provideServerSocketService(ServerSocketService impl) {
+    return impl;
+  }
+
+  @Provides(type = Type.SET)
+  static Service provideTrialOutputFactoryService(TrialOutputFactoryService impl) {
+    return impl;
+  }
+
+  @Provides
+  static TrialOutputFactory provideTrialOutputFactory(TrialOutputFactoryService impl) {
+    return impl;
+  }
+
+  @Provides
+  static ExperimentSelector provideExperimentSelector(FullCartesianExperimentSelector impl) {
+    return impl;
+  }
+
+  @Provides
+  static ListeningExecutorService provideExecutorService(CaliperConfig config) {
+    int poolSize = Integer.parseInt(config.properties().get(RUNNER_MAX_PARALLELISM_OPTION));
+    return MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(poolSize));
+  }
+
+  @LocalPort
+  @Provides
+  static int providePortNumber(ServerSocketService serverSocketService) {
+    return serverSocketService.getPort();
+  }
+
+  /**
+   * Specifies the {@link Class} object to use as a key in the map of available
+   * {@link ResultProcessor result processors} passed to
+   * {@link #provideResultProcessors(CaliperConfig, Map)}.
+   */
+  @MapKey(unwrapValue = true)
+  public @interface ResultProcessorClassKey {
+    Class<? extends ResultProcessor> value();
+  }
+
+  @Provides(type = Type.MAP)
+  @ResultProcessorClassKey(OutputFileDumper.class)
+  static ResultProcessor provideOutputFileDumper(OutputFileDumper impl) {
+    return impl;
+  }
+
+  @Provides(type = Type.MAP)
+  @ResultProcessorClassKey(HttpUploader.class)
+  static ResultProcessor provideHttpUploader(HttpUploader impl) {
+    return impl;
+  }
+
+  @Provides static ImmutableSet<ResultProcessor> provideResultProcessors(
+      CaliperConfig config,
+      Map<Class<? extends ResultProcessor>, Provider<ResultProcessor>> availableProcessors) {
+    ImmutableSet.Builder<ResultProcessor> builder = ImmutableSet.builder();
+    for (Class<? extends ResultProcessor> processorClass : config.getConfiguredResultProcessors()) {
+      Provider<ResultProcessor> resultProcessorProvider = availableProcessors.get(processorClass);
+      ResultProcessor resultProcessor = resultProcessorProvider == null
+          ? ResultProcessorCreator.createResultProcessor(processorClass)
+          : resultProcessorProvider.get();
+      builder.add(resultProcessor);
+    }
+    return builder.build();
+  }
+
+  @Provides static UUID provideUuid() {
+    return UUID.randomUUID();
+  }
+
+  @Provides @BenchmarkParameters
+  static ImmutableSetMultimap<String, String> provideBenchmarkParameters(
+      BenchmarkClass benchmarkClass, CaliperOptions options) throws InvalidBenchmarkException {
+    return benchmarkClass.userParameters().fillInDefaultsFor(options.userParameters());
+  }
+
+  @Provides @Singleton
+  static Host provideHost(EnvironmentGetter environmentGetter) {
+    return environmentGetter.getHost();
+  }
+
+  @Provides @Singleton
+  static EnvironmentGetter provideEnvironmentGetter() {
+    return new EnvironmentGetter();
+  }
+
+  /**
+   * Specifies the {@link Class} object to use as a key in the map of available
+   * {@link Instrument instruments} passed to {@link #provideInstruments},
+   */
+  @MapKey(unwrapValue = true)
+  public @interface InstrumentClassKey {
+    Class<? extends Instrument> value();
+  }
+
+  @Provides(type = Type.MAP)
+  @InstrumentClassKey(ArbitraryMeasurementInstrument.class)
+  static Instrument provideArbitraryMeasurementInstrument() {
+    return new ArbitraryMeasurementInstrument();
+  }
+
+  @Provides(type = Type.MAP)
+  @InstrumentClassKey(AllocationInstrument.class)
+  static Instrument provideAllocationInstrument() {
+    return new AllocationInstrument();
+  }
+
+  @Provides(type = Type.MAP)
+  @InstrumentClassKey(RuntimeInstrument.class)
+  static Instrument provideRuntimeInstrument(
+      @NanoTimeGranularity ShortDuration nanoTimeGranularity) {
+    return new RuntimeInstrument(nanoTimeGranularity);
+  }
+
+  @Provides
+  static ImmutableSet<Instrument> provideInstruments(
+      CaliperOptions options,
+      final CaliperConfig config,
+      Map<Class<? extends Instrument>, Provider<Instrument>> availableInstruments,
+      Platform platform,
+      @Stderr PrintWriter stderr)
+      throws InvalidCommandException {
+
+    ImmutableSet.Builder<Instrument> builder = ImmutableSet.builder();
+    ImmutableSet<String> configuredInstruments = config.getConfiguredInstruments();
+    for (final String instrumentName : options.instrumentNames()) {
+      if (!configuredInstruments.contains(instrumentName)) {
+        throw new InvalidCommandException("%s is not a configured instrument (%s). "
+            + "use --print-config to see the configured instruments.",
+            instrumentName, configuredInstruments);
+      }
+      final InstrumentConfig instrumentConfig = config.getInstrumentConfig(instrumentName);
+      String className = instrumentConfig.className();
+      try {
+        Class<? extends Instrument> clazz =
+            Util.lenientClassForName(className).asSubclass(Instrument.class);
+        Provider<Instrument> instrumentProvider = availableInstruments.get(clazz);
+        if (instrumentProvider == null) {
+          throw new InvalidInstrumentException("Instrument %s not supported", className);
+        }
+
+        // Make sure that the instrument is supported on the platform.
+        if (platform.supports(clazz)) {
+          Instrument instrument = instrumentProvider.get();
+          InstrumentInjectorModule injectorModule =
+              new InstrumentInjectorModule(instrumentConfig, instrumentName);
+          InstrumentComponent instrumentComponent = DaggerInstrumentComponent.builder()
+              .instrumentInjectorModule(injectorModule)
+              .build();
+          instrumentComponent.injectInstrument(instrument);
+          builder.add(instrument);
+        } else {
+          stderr.format("Instrument %s not supported on %s, ignoring\n",
+              className, platform.name());
+        }
+      } catch (ClassNotFoundException e) {
+        throw new InvalidCommandException("Cannot find instrument class '%s'", className);
+      }
+    }
+    return builder.build();
+  }
+
+  @Provides @Singleton static NanoTimeGranularityTester provideNanoTimeGranularityTester() {
+    return new NanoTimeGranularityTester();
+  }
+
+  @Provides @Singleton @NanoTimeGranularity static ShortDuration provideNanoTimeGranularity(
+      NanoTimeGranularityTester tester) {
+    return tester.testNanoTimeGranularity();
+  }
+
+  @Provides static ImmutableSet<Instrumentation> provideInstrumentations(CaliperOptions options,
+      BenchmarkClass benchmarkClass, ImmutableSet<Instrument> instruments)
+          throws InvalidBenchmarkException {
+    ImmutableSet.Builder<Instrumentation> builder = ImmutableSet.builder();
+    ImmutableSet<String> benchmarkMethodNames = options.benchmarkMethodNames();
+    Set<String> unusedBenchmarkNames = new HashSet<String>(benchmarkMethodNames);
+    for (Instrument instrument : instruments) {
+      for (Method method : findAllBenchmarkMethods(benchmarkClass.benchmarkClass(), instrument)) {
+        if (benchmarkMethodNames.isEmpty() || benchmarkMethodNames.contains(method.getName())) {
+          builder.add(instrument.createInstrumentation(method));
+          unusedBenchmarkNames.remove(method.getName());
+        }
+      }
+    }
+    if (!unusedBenchmarkNames.isEmpty()) {
+      throw new InvalidBenchmarkException(
+          "Invalid benchmark method(s) specified in options: " + unusedBenchmarkNames);
+    }
+    return builder.build();
+  }
+
+  private static ImmutableSortedSet<Method> findAllBenchmarkMethods(Class<?> benchmarkClass,
+      Instrument instrument) throws InvalidBenchmarkException {
+    ImmutableSortedSet.Builder<Method> result = ImmutableSortedSet.orderedBy(
+        Ordering.natural().onResultOf(new Function<Method, String>() {
+          @Override public String apply(Method method) {
+            return method.getName();
+          }
+        }));
+    Set<String> benchmarkMethodNames = new HashSet<String>();
+    Set<String> overloadedMethodNames = new TreeSet<String>();
+    for (Method method : benchmarkClass.getDeclaredMethods()) {
+      if (instrument.isBenchmarkMethod(method)) {
+        method.setAccessible(true);
+        result.add(method);
+        if (!benchmarkMethodNames.add(method.getName())) {
+          overloadedMethodNames.add(method.getName());
+        }
+      }
+    }
+    if (!overloadedMethodNames.isEmpty()) {
+      throw new InvalidBenchmarkException(
+          "Overloads are disallowed for benchmark methods, found overloads of %s in benchmark %s",
+          overloadedMethodNames,
+          benchmarkClass);
+    }
+    return result.build();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/FullCartesianExperimentSelector.java b/caliper/src/main/java/com/google/caliper/runner/FullCartesianExperimentSelector.java
new file mode 100644
index 0000000..a972ca4
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/FullCartesianExperimentSelector.java
@@ -0,0 +1,112 @@
+/*
+ * 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.runner;
+
+import com.google.caliper.runner.Instrument.Instrumentation;
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+/**
+ * A set of {@link Experiment experiments} constructed by taking all possible combinations of
+ * instruments, benchmark methods, user parameters, VM specs and VM arguments.
+ */
+public final class FullCartesianExperimentSelector implements ExperimentSelector {
+  private ImmutableSet<Instrumentation> instrumentations;
+  private final ImmutableSet<VirtualMachine> vms;
+  private final ImmutableSetMultimap<String, String> userParameters;
+
+  @Inject FullCartesianExperimentSelector(
+      ImmutableSet<Instrumentation> instrumentations,
+      ImmutableSet<VirtualMachine> vms,
+      @BenchmarkParameters ImmutableSetMultimap<String, String> userParameters) {
+    this.instrumentations = instrumentations;
+    this.vms = vms;
+    this.userParameters = userParameters;
+  }
+
+  // TODO(gak): put this someplace more sensible
+  @Override public ImmutableSet<Instrument> instruments() {
+    return FluentIterable.from(instrumentations)
+        .transform(new Function<Instrumentation, Instrument>() {
+          @Override public Instrument apply(Instrumentation input) {
+            return input.instrument();
+          }
+        })
+        .toSet();
+  }
+
+  @Override public ImmutableSet<VirtualMachine> vms() {
+    return vms;
+  }
+
+  @Override public ImmutableSetMultimap<String, String> userParameters() {
+    return userParameters;
+  }
+
+  @Override public ImmutableSet<Experiment> selectExperiments() {
+    List<Experiment> experiments = Lists.newArrayList();
+    for (Instrumentation instrumentation : instrumentations) {
+      for (VirtualMachine vm : vms) {
+        for (List<String> userParamsChoice : cartesian(userParameters)) {
+          ImmutableMap<String, String> theseUserParams =
+              zip(userParameters.keySet(), userParamsChoice);
+          experiments.add(new Experiment(instrumentation, theseUserParams, vm));
+        }
+      }
+    }
+    return ImmutableSet.copyOf(experiments);
+  }
+
+  protected static <T> Set<List<T>> cartesian(SetMultimap<String, T> multimap) {
+    @SuppressWarnings({"unchecked", "rawtypes"}) // promised by spec
+    ImmutableMap<String, Set<T>> paramsAsMap = (ImmutableMap) multimap.asMap();
+    return Sets.cartesianProduct(paramsAsMap.values().asList());
+  }
+
+  protected static <K, V> ImmutableMap<K, V> zip(Set<K> keys, Collection<V> values) {
+    ImmutableMap.Builder<K, V> builder = ImmutableMap.builder();
+
+    Iterator<K> keyIterator = keys.iterator();
+    Iterator<V> valueIterator = values.iterator();
+
+    while (keyIterator.hasNext() && valueIterator.hasNext()) {
+      builder.put(keyIterator.next(), valueIterator.next());
+    }
+
+    if (keyIterator.hasNext() || valueIterator.hasNext()) {
+      throw new AssertionError(); // I really screwed up, then.
+    }
+    return builder.build();
+  }
+
+  @Override public String selectionType() {
+    return "Full cartesian product";
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/HttpUploader.java b/caliper/src/main/java/com/google/caliper/runner/HttpUploader.java
new file mode 100644
index 0000000..2f99a50
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/HttpUploader.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 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.runner;
+
+import com.google.caliper.api.ResultProcessor;
+import com.google.caliper.config.CaliperConfig;
+import com.google.caliper.config.InvalidConfigurationException;
+import com.google.caliper.util.Stdout;
+import com.google.gson.Gson;
+
+import com.sun.jersey.api.client.Client;
+
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+/**
+ * A {@link ResultProcessor} implementation that publishes results to the webapp using an HTTP
+ * request.
+ */
+public class HttpUploader extends ResultsUploader {
+  @Inject HttpUploader(@Stdout PrintWriter stdout, Gson gson, CaliperConfig config)
+      throws InvalidConfigurationException {
+    super(stdout, gson, Client.create(), config.getResultProcessorConfig(HttpUploader.class));
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/Instrument.java b/caliper/src/main/java/com/google/caliper/runner/Instrument.java
new file mode 100644
index 0000000..0d6bc73
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/Instrument.java
@@ -0,0 +1,231 @@
+/*
+ * 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.runner;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.caliper.bridge.AbstractLogMessageVisitor;
+import com.google.caliper.bridge.LogMessageVisitor;
+import com.google.caliper.bridge.StopMeasurementLogMessage;
+import com.google.caliper.config.VmConfig;
+import com.google.caliper.model.InstrumentSpec;
+import com.google.caliper.model.Measurement;
+import com.google.caliper.worker.Worker;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+
+import java.lang.reflect.Method;
+
+import javax.inject.Inject;
+
+public abstract class Instrument {
+  protected ImmutableMap<String, String> options;
+  private String name = getClass().getSimpleName();
+
+  @Inject void setOptions(@InstrumentOptions ImmutableMap<String, String> options) {
+    this.options = ImmutableMap.copyOf(
+        Maps.filterKeys(options, Predicates.in(instrumentOptions())));
+  }
+
+  @Inject void setInstrumentName(@InstrumentName String name) {
+    this.name = name;
+  }
+
+  String name() {
+    return name;
+  }
+
+  @Override public String toString() {
+    return name();
+  }
+
+  public abstract boolean isBenchmarkMethod(Method method);
+
+  public abstract Instrumentation createInstrumentation(Method benchmarkMethod)
+      throws InvalidBenchmarkException;
+
+  /**
+   * Indicates that trials using this instrument can be run in parallel with other trials.
+   */
+  public abstract TrialSchedulingPolicy schedulingPolicy();
+
+  /**
+   * The application of an instrument to a particular benchmark method.
+   */
+  // TODO(gak): consider passing in Instrument explicitly for DI
+  public abstract class Instrumentation {
+    protected Method benchmarkMethod;
+
+    protected Instrumentation(Method benchmarkMethod) {
+      this.benchmarkMethod = checkNotNull(benchmarkMethod);
+    }
+
+    Instrument instrument() {
+      return Instrument.this;
+    }
+
+    Method benchmarkMethod() {
+      return benchmarkMethod;
+    }
+
+    @Override
+    public final boolean equals(Object obj) {
+      if (obj == this) {
+        return true;
+      } else if (obj instanceof Instrumentation) {
+        Instrumentation that = (Instrumentation) obj;
+        return Instrument.this.equals(that.instrument())
+            && this.benchmarkMethod.equals(that.benchmarkMethod);
+      }
+      return super.equals(obj);
+    }
+
+    @Override
+    public final int hashCode() {
+      return Objects.hashCode(Instrument.this, benchmarkMethod);
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(Instrumentation.class)
+          .add("instrument", Instrument.this)
+          .add("benchmarkMethod", benchmarkMethod)
+          .toString();
+    }
+
+    public abstract void dryRun(Object benchmark) throws InvalidBenchmarkException;
+
+    public abstract Class<? extends Worker> workerClass();
+
+    /**
+     * Return the subset of options (and possibly a transformation thereof) to be used in the
+     * worker. Returns all instrument options by default.
+     */
+    public ImmutableMap<String, String> workerOptions() {
+      return options;
+    }
+
+    abstract MeasurementCollectingVisitor getMeasurementCollectingVisitor();
+  }
+
+  public final ImmutableMap<String, String> options() {
+    return options;
+  }
+
+  final InstrumentSpec getSpec() {
+    return new InstrumentSpec.Builder()
+        .instrumentClass(getClass())
+        .addAllOptions(options())
+        .build();
+  }
+
+  /**
+   * Defines the list of options applicable to this instrument. Implementations that use options
+   * will need to override this method.
+   */
+  protected ImmutableSet<String> instrumentOptions() {
+    return ImmutableSet.of();
+  }
+
+  /**
+   * Returns some arguments that should be added to the command line when invoking
+   * this instrument's worker.
+   *
+   * @param vmConfig the configuration for the VM on which this is running.
+   */
+  ImmutableSet<String> getExtraCommandLineArgs(VmConfig vmConfig) {
+    return vmConfig.commonInstrumentVmArgs();
+  }
+
+  interface MeasurementCollectingVisitor extends LogMessageVisitor {
+    boolean isDoneCollecting();
+    boolean isWarmupComplete();
+    ImmutableList<Measurement> getMeasurements();
+    /**
+     * Returns all the messages created while collecting measurments.
+     * 
+     * <p>A message is some piece of user visible data that should be displayed to the user along
+     * with the trial results.
+     * 
+     * <p>TODO(lukes): should we model these as anything more than strings.  These messages already
+     * have a concept of 'level' based on the prefix.
+     */
+    ImmutableList<String> getMessages();
+  }
+
+  /**
+   * A default implementation of {@link MeasurementCollectingVisitor} that collects measurements for
+   * pre-specified descriptions.
+   */
+  protected static final class DefaultMeasurementCollectingVisitor
+      extends AbstractLogMessageVisitor implements MeasurementCollectingVisitor {
+    static final int DEFAULT_NUMBER_OF_MEASUREMENTS = 9;
+    final ImmutableSet<String> requiredDescriptions;
+    final ListMultimap<String, Measurement> measurementsByDescription;
+    final int requiredMeasurements;
+
+    DefaultMeasurementCollectingVisitor(ImmutableSet<String> requiredDescriptions) {
+      this(requiredDescriptions, DEFAULT_NUMBER_OF_MEASUREMENTS);
+    }
+
+    DefaultMeasurementCollectingVisitor(ImmutableSet<String> requiredDescriptions,
+        int requiredMeasurements) {
+      this.requiredDescriptions = requiredDescriptions;
+      checkArgument(!requiredDescriptions.isEmpty());
+      this.requiredMeasurements = requiredMeasurements;
+      checkArgument(requiredMeasurements > 0);
+      this.measurementsByDescription =
+          ArrayListMultimap.create(requiredDescriptions.size(), requiredMeasurements);
+    }
+
+    @Override public void visit(StopMeasurementLogMessage logMessage) {
+      for (Measurement measurement : logMessage.measurements()) {
+        measurementsByDescription.put(measurement.description(), measurement);
+      }
+    }
+
+    @Override public boolean isDoneCollecting() {
+      for (String description : requiredDescriptions) {
+        if (measurementsByDescription.get(description).size() < requiredMeasurements) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
+    public boolean isWarmupComplete() {
+      return true;
+    }
+
+    @Override public ImmutableList<Measurement> getMeasurements() {
+      return ImmutableList.copyOf(measurementsByDescription.values());
+    }
+
+    @Override public ImmutableList<String> getMessages() {
+      return ImmutableList.of();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/UploadResults.java b/caliper/src/main/java/com/google/caliper/runner/InstrumentComponent.java
similarity index 65%
copy from caliper/src/main/java/com/google/caliper/UploadResults.java
copy to caliper/src/main/java/com/google/caliper/runner/InstrumentComponent.java
index 7dae7af..55157e7 100644
--- a/caliper/src/main/java/com/google/caliper/UploadResults.java
+++ b/caliper/src/main/java/com/google/caliper/runner/InstrumentComponent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2015 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
+package com.google.caliper.runner;
 
-import java.io.File;
+import dagger.Component;
 
 /**
- * Usage: UploadResults <file_or_dir>
+ * Component to use to inject values into an {@link Instrument}.
  */
-public class UploadResults {
-  public static void main(String[] args) {
-    new Runner().uploadResultsFileOrDir(new File(args[0]));
-  }
+@Component(modules = InstrumentInjectorModule.class)
+interface InstrumentComponent {
+
+  void injectInstrument(Instrument instrument);
 }
diff --git a/caliper/src/main/java/com/google/caliper/runner/InstrumentInjectorModule.java b/caliper/src/main/java/com/google/caliper/runner/InstrumentInjectorModule.java
new file mode 100644
index 0000000..dc159e3
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/InstrumentInjectorModule.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 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.runner;
+
+import com.google.caliper.config.InstrumentConfig;
+import com.google.common.collect.ImmutableMap;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * The set of bindings available for injecting into {@link Instrument} instances.
+ */
+@Module
+final class InstrumentInjectorModule {
+
+  private final InstrumentConfig instrumentConfig;
+
+  private final String instrumentName;
+
+  InstrumentInjectorModule(InstrumentConfig instrumentConfig, String instrumentName) {
+    this.instrumentConfig = instrumentConfig;
+    this.instrumentName = instrumentName;
+  }
+
+  @Provides
+  InstrumentConfig provideInstrumentConfig() {
+    return instrumentConfig;
+  }
+
+  @Provides
+  @InstrumentOptions
+  static ImmutableMap<String, String> provideInstrumentOptions(InstrumentConfig config) {
+    return config.options();
+  }
+
+  @Provides
+  @InstrumentName
+  String provideInstrumentName() {
+    return instrumentName;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/InstrumentName.java b/caliper/src/main/java/com/google/caliper/runner/InstrumentName.java
new file mode 100644
index 0000000..bef02c7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/InstrumentName.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/** Binding annotation for the name used to configure an instrument. */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+@interface InstrumentName {}
diff --git a/caliper/src/main/java/com/google/caliper/runner/InstrumentOptions.java b/caliper/src/main/java/com/google/caliper/runner/InstrumentOptions.java
new file mode 100644
index 0000000..d6f0681
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/InstrumentOptions.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 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.runner;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/** Binding annotation for the options applied to an instrument. */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+@interface InstrumentOptions {}
diff --git a/caliper/src/main/java/com/google/caliper/runner/InvalidBenchmarkException.java b/caliper/src/main/java/com/google/caliper/runner/InvalidBenchmarkException.java
new file mode 100644
index 0000000..71aaec2
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/InvalidBenchmarkException.java
@@ -0,0 +1,40 @@
+/*
+ * 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.runner;
+
+import java.io.PrintWriter;
+
+/**
+ *
+ */
+@SuppressWarnings("serial")
+public class InvalidBenchmarkException extends RuntimeException {
+  public InvalidBenchmarkException(String message, Object... args) {
+    super(String.format(message, fixArgs(args)));
+  }
+
+  private static Object[] fixArgs(Object[] args) {
+    for (int i = 0; i < args.length; i++) {
+      if (args[i] instanceof Class) {
+        args[i] = ((Class<?>) args[i]).getSimpleName();
+      }
+    }
+    return args;
+  }
+
+  public void display(PrintWriter writer) {
+    writer.println(getMessage());
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/InvalidInstrumentException.java b/caliper/src/main/java/com/google/caliper/runner/InvalidInstrumentException.java
new file mode 100644
index 0000000..83db165
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/InvalidInstrumentException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.runner;
+
+import java.io.PrintWriter;
+
+/**
+ *
+ */
+@SuppressWarnings("serial")
+public class InvalidInstrumentException extends RuntimeException {
+  public InvalidInstrumentException(String message, Object... args) {
+    super(String.format(message, args));
+  }
+
+  public void display(PrintWriter writer) {
+    printStackTrace(writer);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/JarFinder.java b/caliper/src/main/java/com/google/caliper/runner/JarFinder.java
new file mode 100644
index 0000000..174ef81
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/JarFinder.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2013 The Guava Authors
+ *
+ * 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.runner;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.reflect.ClassPath;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+/**
+ * Scans the source of a {@link ClassLoader} and finds all jar files.  This is a modified version
+ * of {@link ClassPath} that finds jars instead of resources.
+ */
+final class JarFinder {
+  private static final Logger logger = Logger.getLogger(JarFinder.class.getName());
+
+  /** Separator for the Class-Path manifest attribute value in jar files. */
+  private static final Splitter CLASS_PATH_ATTRIBUTE_SEPARATOR =
+      Splitter.on(' ').omitEmptyStrings();
+
+  /**
+   * Returns a list of jar files reachable from the given class loaders.
+   *
+   * <p>Currently only {@link URLClassLoader} and only {@code file://} urls are supported.
+   *
+   * @throws IOException if the attempt to read class path resources (jar files or directories)
+   *         failed.
+   */
+  public static ImmutableSet<File> findJarFiles(ClassLoader first, ClassLoader... rest)
+      throws IOException {
+    Scanner scanner = new Scanner();
+    Map<URI, ClassLoader> map = Maps.newLinkedHashMap();
+    for (ClassLoader classLoader : Lists.asList(first, rest)) {
+      map.putAll(getClassPathEntries(classLoader));
+    }
+    for (Map.Entry<URI, ClassLoader> entry : map.entrySet()) {
+      scanner.scan(entry.getKey(), entry.getValue());
+    }
+    return scanner.jarFiles();
+  }
+
+  @VisibleForTesting static ImmutableMap<URI, ClassLoader> getClassPathEntries(
+      ClassLoader classloader) {
+    Map<URI, ClassLoader> entries = Maps.newLinkedHashMap();
+    // Search parent first, since it's the order ClassLoader#loadClass() uses.
+    ClassLoader parent = classloader.getParent();
+    if (parent != null) {
+      entries.putAll(getClassPathEntries(parent));
+    }
+    if (classloader instanceof URLClassLoader) {
+      URLClassLoader urlClassLoader = (URLClassLoader) classloader;
+      for (URL entry : urlClassLoader.getURLs()) {
+        URI uri;
+        try {
+          uri = entry.toURI();
+        } catch (URISyntaxException e) {
+          throw new IllegalArgumentException(e);
+        }
+        if (!entries.containsKey(uri)) {
+          entries.put(uri, classloader);
+        }
+      }
+    }
+    return ImmutableMap.copyOf(entries);
+  }
+
+  @VisibleForTesting static final class Scanner {
+    private final ImmutableSet.Builder<File> jarFiles = new ImmutableSet.Builder<File>();
+    private final Set<URI> scannedUris = Sets.newHashSet();
+
+    ImmutableSet<File> jarFiles() {
+      return jarFiles.build();
+    }
+
+    void scan(URI uri, ClassLoader classloader) throws IOException {
+      if (uri.getScheme().equals("file") && scannedUris.add(uri)) {
+        scanFrom(new File(uri), classloader);
+      }
+    }
+
+    @VisibleForTesting void scanFrom(File file, ClassLoader classloader)
+        throws IOException {
+      if (!file.exists()) {
+        return;
+      }
+      if (file.isDirectory()) {
+        scanDirectory(file, classloader);
+      } else {
+        scanJar(file, classloader);
+      }
+    }
+
+    private void scanDirectory(File directory, ClassLoader classloader) {
+      scanDirectory(directory, classloader, "");
+    }
+
+    private void scanDirectory(
+        File directory, ClassLoader classloader, String packagePrefix) {
+      for (File file : directory.listFiles()) {
+        String name = file.getName();
+        if (file.isDirectory()) {
+          scanDirectory(file, classloader, packagePrefix + name + "/");
+        }
+        // do we need to look for jars here?
+      }
+    }
+
+    private void scanJar(File file, ClassLoader classloader) throws IOException {
+      JarFile jarFile;
+      try {
+        jarFile = new JarFile(file);
+      } catch (IOException e) {
+        // Not a jar file
+        return;
+      }
+      jarFiles.add(file);
+      try {
+        for (URI uri : getClassPathFromManifest(file, jarFile.getManifest())) {
+          scan(uri, classloader);
+        }
+      } finally {
+        try {
+          jarFile.close();
+        } catch (IOException ignored) {}
+      }
+    }
+
+    /**
+     * Returns the class path URIs specified by the {@code Class-Path} manifest attribute, according
+     * to <a
+     * href="http://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Main%20Attributes">
+     * JAR File Specification</a>. If {@code manifest} is null, it means the jar file has no
+     * manifest, and an empty set will be returned.
+     */
+    @VisibleForTesting static ImmutableSet<URI> getClassPathFromManifest(
+        File jarFile, @Nullable Manifest manifest) {
+      if (manifest == null) {
+        return ImmutableSet.of();
+      }
+      ImmutableSet.Builder<URI> builder = ImmutableSet.builder();
+      String classpathAttribute = manifest.getMainAttributes()
+          .getValue(Attributes.Name.CLASS_PATH.toString());
+      if (classpathAttribute != null) {
+        for (String path : CLASS_PATH_ATTRIBUTE_SEPARATOR.split(classpathAttribute)) {
+          URI uri;
+          try {
+            uri = getClassPathEntry(jarFile, path);
+          } catch (URISyntaxException e) {
+            // Ignore bad entry
+            logger.warning("Invalid Class-Path entry: " + path);
+            continue;
+          }
+          builder.add(uri);
+        }
+      }
+      return builder.build();
+    }
+
+    /**
+     * Returns the absolute uri of the Class-Path entry value as specified in
+     * <a
+     * href="http://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Main%20Attributes">
+     * JAR File Specification</a>. Even though the specification only talks about relative urls,
+     * absolute urls are actually supported too (for example, in Maven surefire plugin).
+     */
+    @VisibleForTesting static URI getClassPathEntry(File jarFile, String path)
+        throws URISyntaxException {
+      URI uri = new URI(path);
+      return uri.isAbsolute()
+          ? uri
+          : new File(jarFile.getParentFile(), path.replace('/', File.separatorChar)).toURI();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/LocalPort.java b/caliper/src/main/java/com/google/caliper/runner/LocalPort.java
new file mode 100644
index 0000000..08f7314
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/LocalPort.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/** Binding annotation for the port to which the worker has bound. */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+@interface LocalPort {}
diff --git a/caliper/src/main/java/com/google/caliper/runner/MainComponent.java b/caliper/src/main/java/com/google/caliper/runner/MainComponent.java
new file mode 100644
index 0000000..22bcd75
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/MainComponent.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 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.runner;
+
+import com.google.caliper.bridge.BridgeModule;
+import com.google.caliper.config.CaliperConfig;
+import com.google.caliper.config.ConfigModule;
+import com.google.caliper.json.GsonModule;
+import com.google.caliper.options.CaliperOptions;
+import com.google.caliper.options.OptionsModule;
+import com.google.caliper.util.OutputModule;
+import com.google.common.util.concurrent.ServiceManager;
+
+import dagger.Component;
+
+import javax.inject.Singleton;
+
+/**
+ * The main component used when running caliper.
+ */
+@Singleton
+@Component(modules = {
+    BenchmarkClassModule.class,
+    BridgeModule.class,
+    ConfigModule.class,
+    ExperimentingRunnerModule.class,
+    GsonModule.class,
+    MainModule.class,
+    OptionsModule.class,
+    OutputModule.class,
+    PlatformModule.class,
+    RunnerModule.class,
+    ServiceModule.class,
+})
+interface MainComponent {
+
+  BenchmarkClass getBenchmarkClass();
+
+  CaliperConfig getCaliperConfig();
+
+  CaliperOptions getCaliperOptions();
+
+  CaliperRun getCaliperRun();
+
+  ServiceManager getServiceManager();
+
+  TrialScopeComponent newTrialComponent(TrialModule trialModule);
+
+  ExperimentComponent newExperimentComponent(ExperimentModule experimentModule);
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/MainModule.java b/caliper/src/main/java/com/google/caliper/runner/MainModule.java
new file mode 100644
index 0000000..86ab6f9
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/MainModule.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 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.runner;
+
+import com.google.caliper.options.CaliperOptions;
+import com.google.caliper.util.InvalidCommandException;
+import com.google.caliper.util.Util;
+import dagger.Module;
+import dagger.Provides;
+import javax.inject.Singleton;
+
+/**
+ * Provides bindings to integrate other modules into the {@link MainComponent}.
+ */
+@Module
+class MainModule {
+
+  private static Class<?> benchmarkClassForName(String className)
+      throws InvalidCommandException, UserCodeException {
+    try {
+      return Util.lenientClassForName(className);
+    } catch (ClassNotFoundException e) {
+      throw new InvalidCommandException("Benchmark class not found: " + className);
+    } catch (ExceptionInInitializerError e) {
+      throw new UserCodeException(
+          "Exception thrown while initializing class '" + className + "'", e.getCause());
+    } catch (NoClassDefFoundError e) {
+      throw new UserCodeException("Unable to load " + className, e);
+    }
+  }
+
+  @Provides
+  @Singleton
+  @Running.BenchmarkClass
+  static Class<?> provideBenchmarkClass(CaliperOptions options) {
+    return benchmarkClassForName(options.benchmarkClassName());
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/NanoTimeGranularity.java b/caliper/src/main/java/com/google/caliper/runner/NanoTimeGranularity.java
new file mode 100644
index 0000000..737b0c7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/NanoTimeGranularity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/** Binding annotation for the granularity of {@link System#nanoTime()}. */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+@interface NanoTimeGranularity {}
diff --git a/caliper/src/main/java/com/google/caliper/runner/NanoTimeGranularityTester.java b/caliper/src/main/java/com/google/caliper/runner/NanoTimeGranularityTester.java
new file mode 100644
index 0000000..ee135bf
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/NanoTimeGranularityTester.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static java.math.RoundingMode.CEILING;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import com.google.caliper.util.ShortDuration;
+import com.google.common.base.Ticker;
+import com.google.common.math.LongMath;
+
+/**
+ * A utility that calculates the finest granularity that can be expected from subsequent calls to
+ * {@link System#nanoTime()}.  Note that this utility necessarily invokes {@link System#nanoTime()}
+ * directly rather than using {@link Ticker} because the extra indirection might cause additional
+ * overhead.
+ */
+final class NanoTimeGranularityTester {
+  private static final int TRIALS = 1000;
+
+  ShortDuration testNanoTimeGranularity() {
+    long total = 0L;
+    for (int i = 0; i < TRIALS; i++) {
+      long first = System.nanoTime();
+      long second = System.nanoTime();
+      long third = System.nanoTime();
+      long fourth = System.nanoTime();
+      long fifth = System.nanoTime();
+      long sixth = System.nanoTime();
+      long seventh = System.nanoTime();
+      long eighth = System.nanoTime();
+      long ninth = System.nanoTime();
+      total += second - first;
+      total += third - second;
+      total += fourth - third;
+      total += fifth - fourth;
+      total += sixth - fifth;
+      total += seventh - sixth;
+      total += eighth - seventh;
+      total += ninth - eighth;
+    }
+    return ShortDuration.of(LongMath.divide(total, TRIALS * 8, CEILING), NANOSECONDS);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/OutputFileDumper.java b/caliper/src/main/java/com/google/caliper/runner/OutputFileDumper.java
new file mode 100644
index 0000000..6a9e892
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/OutputFileDumper.java
@@ -0,0 +1,124 @@
+/*
+ * 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.runner;
+
+import static java.util.logging.Level.SEVERE;
+
+import com.google.caliper.api.ResultProcessor;
+import com.google.caliper.config.CaliperConfig;
+import com.google.caliper.config.InvalidConfigurationException;
+import com.google.caliper.config.ResultProcessorConfig;
+import com.google.caliper.model.Run;
+import com.google.caliper.model.Trial;
+import com.google.caliper.options.CaliperDirectory;
+import com.google.common.base.Charsets;
+import com.google.common.base.Optional;
+import com.google.common.io.Files;
+import com.google.gson.Gson;
+import com.google.gson.stream.JsonWriter;
+
+import org.joda.time.format.ISODateTimeFormat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ResultProcessor} that dumps the output data to a file in JSON format. By default, the
+ * output will be dumped to a file called
+ * {@code ~/.caliper/results/[benchmark classname].[timestamp].json}; if it exists and is a file,
+ * the file will be overwritten.  The location can be overridden as either a file or a directory
+ * using either the {@code file} or {@code dir} options respectively.
+ */
+final class OutputFileDumper implements ResultProcessor {
+  private static final Logger logger = Logger.getLogger(OutputFileDumper.class.getName());
+
+  private final Run run;
+  private final Gson gson;
+  private final File resultFile;
+  private final File workFile;
+
+  private Optional<JsonWriter> writer = Optional.absent();
+
+  @Inject OutputFileDumper(Run run,
+      BenchmarkClass benchmarkClass,
+      Gson gson,
+      CaliperConfig caliperConfig,
+      @CaliperDirectory File caliperDirectory) throws InvalidConfigurationException {
+    this.run = run;
+    ResultProcessorConfig config = caliperConfig.getResultProcessorConfig(OutputFileDumper.class);
+    if (config.options().containsKey("file")) {
+      this.resultFile = new File(config.options().get("file"));
+      logger.finer("found an output file in the configuration");
+    } else if (config.options().containsKey("dir")) {
+      File dir = new File(config.options().get("dir"));
+      if (dir.isFile()) {
+        throw new InvalidConfigurationException("specified a directory, but it's a file");
+      }
+      this.resultFile = new File(dir, createFileName(benchmarkClass.name()));
+      logger.finer("found an output directory in the configuration");
+    } else {
+      this.resultFile =
+          new File(new File(caliperDirectory, "results"), createFileName(benchmarkClass.name()));
+      logger.fine("found no configuration");
+    }
+    logger.fine(String.format("using %s for results", resultFile));
+    this.gson = gson;
+    this.workFile = new File(resultFile.getPath() + ".tmp");
+  }
+
+  private String createFileName(String benchmarkName) {
+    return String.format("%s.%s.json", benchmarkName, createTimestamp());
+  }
+
+  private String createTimestamp() {
+    return ISODateTimeFormat.dateTimeNoMillis().print(run.startTime());
+  }
+
+  @Override public void processTrial(Trial trial) {
+    if (!writer.isPresent()) {
+      try {
+        Files.createParentDirs(workFile);
+        JsonWriter writer =
+            new JsonWriter(new OutputStreamWriter(new FileOutputStream(workFile), Charsets.UTF_8));
+        writer.setIndent("  ");  // always pretty print
+        writer.beginArray();
+        this.writer = Optional.of(writer);
+      } catch (IOException e) {
+        logger.log(SEVERE, String.format(
+            "An error occured writing trial %s. Results in %s will be incomplete.", trial.id(),
+            resultFile), e);
+      }
+    }
+    if (writer.isPresent()) {
+      gson.toJson(trial, Trial.class, writer.get());
+    }
+  }
+
+  @Override public void close() throws IOException {
+    if (writer.isPresent()) {
+      writer.get().endArray().close();
+    }
+    if (workFile.exists()) {
+      Files.move(workFile, resultFile);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/Parameter.java b/caliper/src/main/java/com/google/caliper/runner/Parameter.java
new file mode 100644
index 0000000..25d9484
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/Parameter.java
@@ -0,0 +1,133 @@
+/*
+ * 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.runner;
+
+import com.google.caliper.Param;
+import com.google.caliper.util.Parser;
+import com.google.caliper.util.Parsers;
+import com.google.caliper.util.Util;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.primitives.Primitives;
+
+import java.lang.reflect.Field;
+import java.text.ParseException;
+
+/**
+ * Represents an injectable parameter, marked with one of @Param, @VmParam. Has nothing to do with
+ * particular choices of <i>values</i> for this parameter (except that it knows how to find the
+ * <i>default</i> values).
+ */
+public final class Parameter {
+  public static Parameter create(Field field) throws InvalidBenchmarkException {
+    return new Parameter(field);
+  }
+
+  private final Field field;
+  private final Parser<?> parser;
+  private final ImmutableList<String> defaults;
+
+  public Parameter(Field field) throws InvalidBenchmarkException {
+    if (Util.isStatic(field)) {
+      throw new InvalidBenchmarkException("Parameter field '%s' must not be static",
+          field.getName());
+    }
+    if (RESERVED_NAMES.contains(field.getName())) {
+      throw new InvalidBenchmarkException("Class '%s' uses reserved parameter name '%s'",
+          field.getDeclaringClass(), field.getName());
+    }
+
+    this.field = field;
+    field.setAccessible(true);
+
+    Class<?> type = Primitives.wrap(field.getType());
+    try {
+      this.parser = Parsers.conventionalParser(type);
+    } catch (NoSuchMethodException e) {
+      throw new InvalidBenchmarkException("Type '%s' of parameter field '%s' has no recognized "
+          + "String-converting method; see <TODO> for details", type, field.getName());
+    }
+
+    this.defaults = findDefaults(field);
+    validate(defaults);
+  }
+
+  void validate(ImmutableCollection<String> values) throws InvalidBenchmarkException {
+    for (String valueAsString : values) {
+      try {
+        parser.parse(valueAsString);
+      } catch (ParseException e) {
+        throw new InvalidBenchmarkException(
+            "Cannot convert value '%s' to type '%s': %s",
+            valueAsString, field.getType(), e.getMessage());
+      }
+    }
+  }
+
+  static final ImmutableSet<String> RESERVED_NAMES = ImmutableSet.of(
+      "benchmark",
+      "environment",
+      "instrument",
+      "measurement", // e.g. runtime, allocation, etc.
+      "run",
+      "trial", // currently unused, but we might need it
+      "vm");
+
+  String name() {
+    return field.getName();
+  }
+
+  ImmutableList<String> defaults() {
+    return defaults;
+  }
+
+  void inject(Object benchmark, String value) {
+    try {
+      Object o = parser.parse(value);
+      field.set(benchmark, o);
+    } catch (ParseException impossible) {
+      // already validated both defaults and command-line
+      throw new AssertionError(impossible);
+    } catch (IllegalAccessException impossible) {
+      throw new AssertionError(impossible);
+    }
+  }
+
+  private static ImmutableList<String> findDefaults(Field field) {
+    String[] defaultsAsStrings = field.getAnnotation(Param.class).value();
+    if (defaultsAsStrings.length > 0) {
+      return ImmutableList.copyOf(defaultsAsStrings);
+    }
+
+    Class<?> type = field.getType();
+    if (type == boolean.class) {
+      return ALL_BOOLEANS;
+    }
+
+    if (type.isEnum()) {
+      ImmutableList.Builder<String> builder = ImmutableList.builder();
+      for (Object enumConstant : type.getEnumConstants()) {
+        builder.add(enumConstant.toString());
+      }
+      return builder.build();
+    }
+    return ImmutableList.of();
+  }
+
+  private static final ImmutableList<String> ALL_BOOLEANS = ImmutableList.of("true", "false");
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ParameterSet.java b/caliper/src/main/java/com/google/caliper/runner/ParameterSet.java
new file mode 100644
index 0000000..640e52f
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ParameterSet.java
@@ -0,0 +1,92 @@
+/*
+ * 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.runner;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.Sets;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents all the injectable parameter fields of a single kind (@Param or @VmParam) found in a
+ * benchmark class. Has nothing to do with particular choices of <i>values</i> for these parameters
+ * (except that it knows how to find the <i>default</i> values).
+ */
+public final class ParameterSet {
+  public static ParameterSet create(Class<?> theClass, Class<? extends Annotation> annotationClass)
+      throws InvalidBenchmarkException {
+    // deterministic order, not reflection order
+    ImmutableMap.Builder<String, Parameter> parametersBuilder =
+        ImmutableSortedMap.naturalOrder();
+
+    for (Field field : theClass.getDeclaredFields()) {
+      if (field.isAnnotationPresent(annotationClass)) {
+        Parameter parameter = Parameter.create(field);
+        parametersBuilder.put(field.getName(), parameter);
+      }
+    }
+    return new ParameterSet(parametersBuilder.build());
+  }
+
+  final ImmutableMap<String, Parameter> map;
+
+  private ParameterSet(ImmutableMap<String, Parameter> map) {
+    this.map = map;
+  }
+
+  public Set<String> names() {
+    return map.keySet();
+  }
+
+  public Parameter get(String name) {
+    return map.get(name);
+  }
+
+  public ImmutableSetMultimap<String, String> fillInDefaultsFor(
+      ImmutableSetMultimap<String, String> explicitValues) throws InvalidBenchmarkException {
+    ImmutableSetMultimap.Builder<String, String> combined = ImmutableSetMultimap.builder();
+
+    // For user parameters, this'll actually be the same as fromClass.keySet(), since any extras
+    // given at the command line are treated as errors; for VM parameters this is not the case.
+    for (String name : Sets.union(map.keySet(), explicitValues.keySet())) {
+      Parameter parameter = map.get(name);
+      ImmutableCollection<String> values = explicitValues.containsKey(name)
+          ? explicitValues.get(name)
+          : parameter.defaults();
+
+      combined.putAll(name, values);
+      if (values.isEmpty()) {
+        throw new InvalidBenchmarkException("ERROR: No default value provided for " + name);
+      }
+    }
+    return combined.orderKeysBy(Ordering.natural()).build();
+  }
+
+  public void injectAll(Object benchmark, Map<String, String> actualValues) {
+    for (Parameter parameter : map.values()) {
+      String value = actualValues.get(parameter.name());
+      parameter.inject(benchmark, value);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/PlatformModule.java b/caliper/src/main/java/com/google/caliper/runner/PlatformModule.java
new file mode 100644
index 0000000..2e61989
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/PlatformModule.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 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.runner;
+
+import com.google.caliper.platform.Platform;
+import com.google.caliper.platform.dalvik.DalvikModule;
+import com.google.caliper.platform.dalvik.DalvikPlatform;
+import com.google.caliper.platform.jvm.JvmModule;
+import com.google.caliper.platform.jvm.JvmPlatform;
+import com.google.common.base.Optional;
+
+import dagger.Module;
+import dagger.Provides;
+
+import javax.inject.Provider;
+
+/**
+ * Provider of a {@link Platform} instance appropriate for the current platform.
+ */
+@Module(includes = {JvmModule.class, DalvikModule.class})
+public final class PlatformModule {
+
+  /**
+   * Chooses the {@link DalvikPlatform} if available, otherwise uses the default
+   * {@link JvmPlatform}.
+   */
+  @Provides
+  static Platform providePlatform(
+      Optional<DalvikPlatform> optionalDalvikPlatform,
+      Provider<JvmPlatform> jvmPlatformProvider) {
+    if (optionalDalvikPlatform.isPresent()) {
+      return optionalDalvikPlatform.get();
+    } else {
+      return jvmPlatformProvider.get();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ProxyWorkerException.java b/caliper/src/main/java/com/google/caliper/runner/ProxyWorkerException.java
new file mode 100644
index 0000000..7b814d0
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ProxyWorkerException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.runner;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+
+
+/**
+ * An exception created on the runner with the same stack trace as one thrown on the worker that
+ * reports the actual exception class and message in its message.
+ */
+final class ProxyWorkerException extends RuntimeException {
+  ProxyWorkerException(String stackTrace) {
+    super(formatMesssage(stackTrace));
+  }
+
+  private static String formatMesssage(String stackTrace) {
+    StringBuilder builder = new StringBuilder(stackTrace.length() + 512)
+        .append("An exception occurred in a worker process.  The stack trace is as follows:\n\t");
+    Joiner.on("\n\t").appendTo(builder, Splitter.on('\n').split(stackTrace));
+    return builder.toString();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ResultProcessorCreator.java b/caliper/src/main/java/com/google/caliper/runner/ResultProcessorCreator.java
new file mode 100644
index 0000000..fc278c5
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ResultProcessorCreator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 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.runner;
+
+import com.google.caliper.api.ResultProcessor;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Responsible for creating instances of configured {@link ResultProcessor}.
+ */
+final class ResultProcessorCreator {
+
+  public static final String NO_PUBLIC_DEFAULT_CONSTRUCTOR =
+      "ResultProcessor %s not supported as it does not have a public default constructor";
+
+  private ResultProcessorCreator() {
+  }
+
+  static ResultProcessor createResultProcessor(Class<? extends ResultProcessor> processorClass) {
+    ResultProcessor resultProcessor;
+
+    try {
+      Constructor<? extends ResultProcessor> constructor = processorClass.getConstructor();
+      resultProcessor = constructor.newInstance();
+    } catch (NoSuchMethodException e) {
+      throw new UserCodeException(String.format(NO_PUBLIC_DEFAULT_CONSTRUCTOR, processorClass), e);
+    } catch (InvocationTargetException e) {
+      throw new UserCodeException("ResultProcessor %s could not be instantiated", e.getCause());
+    } catch (InstantiationException e) {
+      throw new UserCodeException("ResultProcessor %s could not be instantiated", e);
+    } catch (IllegalAccessException e) {
+      throw new UserCodeException("ResultProcessor %s could not be instantiated", e);
+    }
+
+    return resultProcessor;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ResultsUploader.java b/caliper/src/main/java/com/google/caliper/runner/ResultsUploader.java
new file mode 100644
index 0000000..8e654d5
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ResultsUploader.java
@@ -0,0 +1,137 @@
+/*
+ * 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.runner;
+
+import static java.util.logging.Level.SEVERE;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
+
+import com.google.caliper.api.ResultProcessor;
+import com.google.caliper.config.InvalidConfigurationException;
+import com.google.caliper.config.ResultProcessorConfig;
+import com.google.caliper.model.Trial;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.Gson;
+
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.ClientHandlerException;
+import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.api.client.WebResource;
+
+import java.io.PrintWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+/**
+ * {@link ResultProcessor} implementation that uploads the JSON-serialized results to the Caliper
+ * webapp.
+ */
+abstract class ResultsUploader implements ResultProcessor {
+  private static final Logger logger = Logger.getLogger(ResultsUploader.class.getName());
+  private static final String POST_PATH = "/data/trials";
+  private static final String RESULTS_PATH_PATTERN = "/runs/%s";
+
+  private final PrintWriter stdout;
+  private final Client client;
+  private final Gson gson;
+  private final Optional<UUID> apiKey;
+  private final Optional<URI> uploadUri;
+  private Optional<UUID> runId = Optional.absent();
+  private boolean failure = false;
+
+  ResultsUploader(PrintWriter stdout, Gson gson, Client client,
+      ResultProcessorConfig resultProcessorConfig) throws InvalidConfigurationException {
+    this.stdout = stdout;
+    this.client = client;
+    this.gson = gson;
+    @Nullable String apiKeyString = resultProcessorConfig.options().get("key");
+    Optional<UUID> apiKey = Optional.absent();
+    if (Strings.isNullOrEmpty(apiKeyString)) {
+      logger.info("No api key specified. Uploading results anonymously.");
+    } else {
+      try {
+        apiKey = Optional.of(UUID.fromString(apiKeyString));
+      } catch (IllegalArgumentException e) {
+        throw new InvalidConfigurationException(String.format(
+            "The specified API key (%s) is not valid. API keys are UUIDs and should look like %s.",
+                apiKeyString, new UUID(0L,  0L)));
+      }
+    }
+    this.apiKey = apiKey;
+
+    @Nullable String urlString = resultProcessorConfig.options().get("url");
+    if (Strings.isNullOrEmpty(urlString)) {
+      logger.info("No upload URL was specified. Results will not be uploaded.");
+      this.uploadUri = Optional.absent();
+    } else {
+      try {
+        this.uploadUri = Optional.of(new URI(urlString).resolve(POST_PATH));
+      } catch (URISyntaxException e) {
+        throw new InvalidConfigurationException(urlString + " is an invalid upload url", e);
+      }
+    }
+  }
+
+  @Override public final void processTrial(Trial trial) {
+    if (uploadUri.isPresent()) {
+      WebResource resource = client.resource(uploadUri.get());
+      if (apiKey.isPresent()) {
+        resource = resource.queryParam("key", apiKey.get().toString());
+      }
+      boolean threw = true;
+      try {
+        // TODO(gak): make the json part happen automagically
+        resource.type(APPLICATION_JSON_TYPE).post(gson.toJson(ImmutableList.of(trial)));
+        // only set the run id if a result has been successfully uploaded
+        runId = Optional.of(trial.run().id());
+        threw = false;
+      } catch (ClientHandlerException e) {
+        logUploadFailure(trial, e);
+      } catch (UniformInterfaceException e) {
+        logUploadFailure(trial, e);
+        logger.fine("Failed upload response: " + e.getResponse().getStatus());
+      } finally {
+        failure |= threw;
+      }
+    }
+  }
+
+  private static void logUploadFailure(Trial trial, Exception e) {
+    logger.log(SEVERE, String.format(
+        "Could not upload trial %s. Consider uploading it manually.", trial.id()), e);
+  }
+
+  @Override public final void close() {
+    if (uploadUri.isPresent()) {
+      if (runId.isPresent()) {
+        stdout.printf("Results have been uploaded. View them at: %s%n",
+            uploadUri.get().resolve(String.format(RESULTS_PATH_PATTERN, runId.get())));
+      }
+      if (failure) {
+        // TODO(gak): implement some retry
+        stdout.println("Some trials failed to upload. Consider uploading them manually.");
+      }
+    } else {
+      logger.fine("No upload URL was provided, so results were not uploaded.");
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/RunnerModule.java b/caliper/src/main/java/com/google/caliper/runner/RunnerModule.java
new file mode 100644
index 0000000..a55fb03
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/RunnerModule.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012 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.runner;
+
+import com.google.caliper.config.CaliperConfig;
+import com.google.caliper.config.InvalidConfigurationException;
+import com.google.caliper.config.VmConfig;
+import com.google.caliper.model.Run;
+import com.google.caliper.options.CaliperOptions;
+import com.google.caliper.platform.Platform;
+import com.google.common.collect.ImmutableSet;
+
+import dagger.Module;
+import dagger.Provides;
+
+import org.joda.time.Instant;
+
+import java.util.UUID;
+
+import javax.inject.Singleton;
+
+/**
+ * A Dagger module that configures bindings common to all {@link CaliperRun} implementations.
+ */
+// TODO(gak): throwing providers for all of the things that throw
+@Module
+final class RunnerModule {
+  @Provides
+  static ImmutableSet<VirtualMachine> provideVirtualMachines(
+      CaliperOptions options,
+      CaliperConfig config,
+      Platform platform)
+      throws InvalidConfigurationException {
+    ImmutableSet<String> vmNames = options.vmNames();
+    ImmutableSet.Builder<VirtualMachine> builder = ImmutableSet.builder();
+    if (vmNames.isEmpty()) {
+      builder.add(new VirtualMachine("default", config.getDefaultVmConfig(platform)));
+    } else {
+      for (String vmName : vmNames) {
+        VmConfig vmConfig = config.getVmConfig(platform, vmName);
+        builder.add(new VirtualMachine(vmName, vmConfig));
+      }
+    }
+    return builder.build();
+  }
+
+  @Provides
+  static Instant provideInstant() {
+    return Instant.now();
+  }
+
+  @Provides static CaliperRun provideCaliperRun(ExperimentingCaliperRun experimentingCaliperRun) {
+    return experimentingCaliperRun;
+  }
+
+  @Provides @Singleton
+  static Run provideRun(UUID uuid, CaliperOptions caliperOptions, Instant startTime) {
+    return new Run.Builder(uuid).label(caliperOptions.runName()).startTime(startTime).build();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/Running.java b/caliper/src/main/java/com/google/caliper/runner/Running.java
new file mode 100644
index 0000000..0ff4c7e
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/Running.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/**
+ * A collection of annotations for bindings pertaining to the currently running experiment.
+ */
+public class Running {
+  private Running() {}
+
+  @Retention(RUNTIME)
+  @Target({FIELD, PARAMETER, METHOD})
+  @Qualifier
+  public @interface Benchmark {}
+
+  @Retention(RUNTIME)
+  @Target({FIELD, PARAMETER, METHOD})
+  @Qualifier
+  public @interface BenchmarkMethod {}
+
+  @Retention(RUNTIME)
+  @Target({FIELD, PARAMETER, METHOD})
+  @Qualifier
+  public @interface BenchmarkClass {}
+
+  @Retention(RUNTIME)
+  @Target({FIELD, PARAMETER, METHOD})
+  @Qualifier
+  public @interface BeforeExperimentMethods {}
+
+  @Retention(RUNTIME)
+  @Target({FIELD, PARAMETER, METHOD})
+  @Qualifier
+  public @interface AfterExperimentMethods {}
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/RuntimeInstrument.java b/caliper/src/main/java/com/google/caliper/runner/RuntimeInstrument.java
new file mode 100644
index 0000000..3187dfd
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/RuntimeInstrument.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static com.google.caliper.runner.CommonInstrumentOptions.GC_BEFORE_EACH_OPTION;
+import static com.google.caliper.runner.CommonInstrumentOptions.MAX_WARMUP_WALL_TIME_OPTION;
+import static com.google.caliper.runner.CommonInstrumentOptions.MEASUREMENTS_OPTION;
+import static com.google.caliper.runner.CommonInstrumentOptions.WARMUP_OPTION;
+import static com.google.caliper.util.Reflection.getAnnotatedMethods;
+import static com.google.caliper.util.Util.isStatic;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Throwables.propagateIfInstanceOf;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import com.google.caliper.Benchmark;
+import com.google.caliper.api.AfterRep;
+import com.google.caliper.api.BeforeRep;
+import com.google.caliper.api.Macrobenchmark;
+import com.google.caliper.api.SkipThisScenarioException;
+import com.google.caliper.bridge.AbstractLogMessageVisitor;
+import com.google.caliper.bridge.GcLogMessage;
+import com.google.caliper.bridge.HotspotLogMessage;
+import com.google.caliper.bridge.StartMeasurementLogMessage;
+import com.google.caliper.bridge.StopMeasurementLogMessage;
+import com.google.caliper.model.Measurement;
+import com.google.caliper.platform.Platform;
+import com.google.caliper.platform.SupportedPlatform;
+import com.google.caliper.util.ShortDuration;
+import com.google.caliper.worker.MacrobenchmarkWorker;
+import com.google.caliper.worker.RuntimeWorker;
+import com.google.caliper.worker.Worker;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+/**
+ * The instrument responsible for measuring the runtime of {@link Benchmark} methods.
+ */
+@SupportedPlatform({Platform.Type.JVM, Platform.Type.DALVIK})
+class RuntimeInstrument extends Instrument {
+  private static final String SUGGEST_GRANULARITY_OPTION = "suggestGranularity";
+  private static final String TIMING_INTERVAL_OPTION = "timingInterval";
+  private static final int DRY_RUN_REPS = 1;
+
+  private static final Logger logger = Logger.getLogger(RuntimeInstrument.class.getName());
+
+  private final ShortDuration nanoTimeGranularity;
+
+  @Inject
+  RuntimeInstrument(@NanoTimeGranularity ShortDuration nanoTimeGranularity) {
+    this.nanoTimeGranularity = nanoTimeGranularity;
+  }
+
+  @Override
+  public boolean isBenchmarkMethod(Method method) {
+    return method.isAnnotationPresent(Benchmark.class)
+        || BenchmarkMethods.isTimeMethod(method)
+        || method.isAnnotationPresent(Macrobenchmark.class);
+  }
+
+  @Override
+  protected ImmutableSet<String> instrumentOptions() {
+    return ImmutableSet.of(
+        WARMUP_OPTION, MAX_WARMUP_WALL_TIME_OPTION, TIMING_INTERVAL_OPTION, MEASUREMENTS_OPTION,
+        GC_BEFORE_EACH_OPTION, SUGGEST_GRANULARITY_OPTION);
+  }
+
+  @Override
+  public Instrumentation createInstrumentation(Method benchmarkMethod)
+      throws InvalidBenchmarkException {
+    checkNotNull(benchmarkMethod);
+    checkArgument(isBenchmarkMethod(benchmarkMethod));
+    if (isStatic(benchmarkMethod)) {
+      throw new InvalidBenchmarkException("Benchmark methods must not be static: %s",
+          benchmarkMethod.getName());
+    }
+    try {
+      switch (BenchmarkMethods.Type.of(benchmarkMethod)) {
+        case MACRO:
+          return new MacrobenchmarkInstrumentation(benchmarkMethod);
+        case MICRO:
+          return new MicrobenchmarkInstrumentation(benchmarkMethod);
+        case PICO:
+          return new PicobenchmarkInstrumentation(benchmarkMethod);
+        default:
+          throw new AssertionError("unknown type");
+      }
+    } catch (IllegalArgumentException e) {
+      throw new InvalidBenchmarkException("Benchmark methods must have no arguments or accept "
+          + "a single int or long parameter: %s", benchmarkMethod.getName());
+    }
+  }
+
+  private class MacrobenchmarkInstrumentation extends Instrumentation {
+    MacrobenchmarkInstrumentation(Method benchmarkMethod) {
+      super(benchmarkMethod);
+    }
+
+    @Override
+    public void dryRun(Object benchmark) throws UserCodeException {
+      ImmutableSet<Method> beforeRepMethods =
+          getAnnotatedMethods(benchmarkMethod.getDeclaringClass(), BeforeRep.class);
+      ImmutableSet<Method> afterRepMethods =
+          getAnnotatedMethods(benchmarkMethod.getDeclaringClass(), AfterRep.class);
+      try {
+        for (Method beforeRepMethod : beforeRepMethods) {
+          beforeRepMethod.invoke(benchmark);
+        }
+        try {
+          benchmarkMethod.invoke(benchmark);
+        } finally {
+          for (Method afterRepMethod : afterRepMethods) {
+            afterRepMethod.invoke(benchmark);
+          }
+        }
+      } catch (IllegalAccessException e) {
+        throw new AssertionError(e);
+      } catch (InvocationTargetException e) {
+        Throwable userException = e.getCause();
+        propagateIfInstanceOf(userException, SkipThisScenarioException.class);
+        throw new UserCodeException(userException);
+      }
+    }
+
+    @Override
+    public Class<? extends Worker> workerClass() {
+      return MacrobenchmarkWorker.class;
+    }
+
+    @Override
+    MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
+      return new SingleInvocationMeasurementCollector(
+          Integer.parseInt(options.get(MEASUREMENTS_OPTION)),
+          ShortDuration.valueOf(options.get(WARMUP_OPTION)),
+          ShortDuration.valueOf(options.get(MAX_WARMUP_WALL_TIME_OPTION)),
+          nanoTimeGranularity);
+    }
+  }
+
+  @Override public TrialSchedulingPolicy schedulingPolicy() {
+    // Runtime measurements are currently believed to be too sensitive to system performance to
+    // allow parallel runners.
+    // TODO(lukes): investigate this, on a multicore system it seems like we should be able to
+    // allow some parallelism without corrupting results.
+    return TrialSchedulingPolicy.SERIAL;
+  }
+
+  private abstract class RuntimeInstrumentation extends Instrumentation {
+    RuntimeInstrumentation(Method method) {
+      super(method);
+    }
+
+    @Override public void dryRun(Object benchmark) throws UserCodeException {
+      try {
+        benchmarkMethod.invoke(benchmark, DRY_RUN_REPS);
+      } catch (IllegalAccessException impossible) {
+        throw new AssertionError(impossible);
+      } catch (InvocationTargetException e) {
+        Throwable userException = e.getCause();
+        propagateIfInstanceOf(userException, SkipThisScenarioException.class);
+        throw new UserCodeException(userException);
+      }
+    }
+
+    @Override public ImmutableMap<String, String> workerOptions() {
+      return ImmutableMap.of(
+          TIMING_INTERVAL_OPTION + "Nanos", toNanosString(TIMING_INTERVAL_OPTION),
+          GC_BEFORE_EACH_OPTION, options.get(GC_BEFORE_EACH_OPTION));
+    }
+
+    private String toNanosString(String optionName) {
+      return String.valueOf(
+          ShortDuration.valueOf(options.get(optionName)).to(NANOSECONDS));
+    }
+
+    @Override MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
+      return new RepBasedMeasurementCollector(
+          getMeasurementsPerTrial(),
+          ShortDuration.valueOf(options.get(WARMUP_OPTION)),
+          ShortDuration.valueOf(options.get(MAX_WARMUP_WALL_TIME_OPTION)),
+          Boolean.parseBoolean(options.get(SUGGEST_GRANULARITY_OPTION)),
+          nanoTimeGranularity);
+    }
+  }
+
+  private class MicrobenchmarkInstrumentation extends RuntimeInstrumentation {
+    MicrobenchmarkInstrumentation(Method benchmarkMethod) {
+      super(benchmarkMethod);
+    }
+
+    @Override public Class<? extends Worker> workerClass() {
+      return RuntimeWorker.Micro.class;
+    }
+  }
+
+  private int getMeasurementsPerTrial() {
+    @Nullable
+    String measurementsString = options.get(MEASUREMENTS_OPTION);
+    int measurementsPerTrial =
+        (measurementsString == null) ? 1 : Integer.parseInt(measurementsString);
+    // TODO(gak): fail faster
+    checkState(measurementsPerTrial > 0);
+    return measurementsPerTrial;
+  }
+
+  private class PicobenchmarkInstrumentation extends RuntimeInstrumentation {
+    PicobenchmarkInstrumentation(Method benchmarkMethod) {
+      super(benchmarkMethod);
+    }
+
+    @Override public Class<? extends Worker> workerClass() {
+      return RuntimeWorker.Pico.class;
+    }
+  }
+
+  private abstract static class RuntimeMeasurementCollector extends AbstractLogMessageVisitor
+      implements MeasurementCollectingVisitor {
+    final int targetMeasurements;
+    final ShortDuration warmup;
+    final ShortDuration maxWarmupWallTime;
+    final List<Measurement> measurements = Lists.newArrayList();
+    ShortDuration elapsedWarmup = ShortDuration.zero();
+    boolean measuring = false;
+    boolean invalidateMeasurements = false;
+    boolean notifiedAboutGc = false;
+    boolean notifiedAboutJit = false;
+    boolean notifiedAboutMeasuringJit = false;
+    Stopwatch timeSinceStartOfTrial = Stopwatch.createUnstarted();
+    final List<String> messages = Lists.newArrayList();
+    final ShortDuration nanoTimeGranularity;
+
+    RuntimeMeasurementCollector(
+        int targetMeasurements,
+        ShortDuration warmup,
+        ShortDuration maxWarmupWallTime,
+        ShortDuration nanoTimeGranularity) {
+      this.targetMeasurements = targetMeasurements;
+      this.warmup = warmup;
+      this.maxWarmupWallTime = maxWarmupWallTime;
+      this.nanoTimeGranularity = nanoTimeGranularity;
+    }
+
+    @Override
+    public void visit(GcLogMessage logMessage) {
+      if (measuring && isWarmupComplete() && !notifiedAboutGc) {
+        gcWhileMeasuring();
+        notifiedAboutGc = true;
+      }
+    }
+
+    abstract void gcWhileMeasuring();
+
+    @Override
+    public void visit(HotspotLogMessage logMessage) {
+      if (isWarmupComplete()) {
+        if (measuring && notifiedAboutMeasuringJit) {
+          hotspotWhileMeasuring();
+          notifiedAboutMeasuringJit = true;
+        } else if (notifiedAboutJit) {
+          hotspotWhileNotMeasuring();
+          notifiedAboutJit = true;
+        }
+      }
+    }
+
+    abstract void hotspotWhileMeasuring();
+
+    abstract void hotspotWhileNotMeasuring();
+
+    @Override
+    public void visit(StartMeasurementLogMessage logMessage) {
+      checkState(!measuring);
+      measuring = true;
+      if (!timeSinceStartOfTrial.isRunning()) {
+        timeSinceStartOfTrial.start();
+      }
+    }
+
+    @Override
+    public void visit(StopMeasurementLogMessage logMessage) {
+      checkState(measuring);
+      ImmutableList<Measurement> newMeasurements = logMessage.measurements();
+      if (!isWarmupComplete()) {
+        for (Measurement measurement : newMeasurements) {
+          // TODO(gak): eventually we will need to resolve different units
+          checkArgument("ns".equals(measurement.value().unit()));
+          elapsedWarmup = elapsedWarmup.plus(
+              ShortDuration.of(BigDecimal.valueOf(measurement.value().magnitude()), NANOSECONDS));
+          validateMeasurement(measurement);
+        }
+      } else {
+        if (!measuredWarmupDurationReached()) {
+          messages.add(String.format(
+              "WARNING: Warmup was interrupted because it took longer than %s of wall-clock time. "
+                  + "%s was spent in the benchmark method for warmup "
+                  + "(normal warmup duration should be %s).",
+              maxWarmupWallTime, elapsedWarmup, warmup));
+        }
+
+        if (invalidateMeasurements) {
+          logger.fine(String.format("Discarding %s as they were marked invalid.", newMeasurements));
+        } else {
+          this.measurements.addAll(newMeasurements);
+        }
+      }
+      invalidateMeasurements = false;
+      measuring = false;
+    }
+
+    abstract void validateMeasurement(Measurement measurement);
+
+    @Override
+    public ImmutableList<Measurement> getMeasurements() {
+      return ImmutableList.copyOf(measurements);
+    }
+
+    boolean measuredWarmupDurationReached() {
+      return elapsedWarmup.compareTo(warmup) >= 0;
+    }
+
+    @Override
+    public boolean isWarmupComplete() {
+      // Fast macro-benchmarks (up to tens of ms) need lots of measurements to reach 10s of
+      // measured warmup time. Because of the per-measurement overhead of running @BeforeRep and
+      // @AfterRep, warmup can take very long.
+      //
+      // To prevent this, we enforce a cap on the wall-clock time here.
+      return measuredWarmupDurationReached()
+          || timeSinceStartOfTrial.elapsed(MILLISECONDS) > maxWarmupWallTime.to(MILLISECONDS);
+    }
+
+    @Override
+    public boolean isDoneCollecting() {
+      return measurements.size() >= targetMeasurements;
+    }
+
+    @Override
+    public ImmutableList<String> getMessages() {
+      return ImmutableList.copyOf(messages);
+    }
+  }
+
+  private static final class RepBasedMeasurementCollector extends RuntimeMeasurementCollector {
+    final boolean suggestGranularity;
+    boolean notifiedAboutGranularity = false;
+
+    RepBasedMeasurementCollector(
+        int measurementsPerTrial,
+        ShortDuration warmup,
+        ShortDuration maxWarmupWallTime,
+        boolean suggestGranularity,
+        ShortDuration nanoTimeGranularity) {
+      super(measurementsPerTrial, warmup, maxWarmupWallTime, nanoTimeGranularity);
+      this.suggestGranularity = suggestGranularity;
+    }
+
+    @Override
+    void gcWhileMeasuring() {
+      invalidateMeasurements = true;
+      messages.add("ERROR: GC occurred during timing. Measurements were discarded.");
+    }
+
+    @Override
+    void hotspotWhileMeasuring() {
+      invalidateMeasurements = true;
+      messages.add(
+          "ERROR: Hotspot compilation occurred during timing: warmup is likely insufficent. "
+              + "Measurements were discarded.");
+    }
+
+    @Override
+    void hotspotWhileNotMeasuring() {
+      messages.add(
+          "WARNING: Hotspot compilation occurred after warmup, but outside of timing. "
+                + "Results may be affected. Run with --verbose to see which method was compiled.");
+    }
+
+    @Override
+    void validateMeasurement(Measurement measurement) {
+      if (suggestGranularity) {
+        double nanos = measurement.value().magnitude() / measurement.weight();
+        if (!notifiedAboutGranularity && ((nanos / 1000) > nanoTimeGranularity.to(NANOSECONDS))) {
+          notifiedAboutGranularity = true;
+          ShortDuration reasonableUpperBound = nanoTimeGranularity.times(1000);
+          messages.add(String.format("INFO: This experiment does not require a microbenchmark. "
+              + "The granularity of the timer (%s) is less than 0.1%% of the measured runtime. "
+              + "If all experiments for this benchmark have runtimes greater than %s, "
+              + "consider the macrobenchmark instrument.", nanoTimeGranularity,
+              reasonableUpperBound));
+        }
+      }
+    }
+  }
+
+  private static final class SingleInvocationMeasurementCollector
+      extends RuntimeMeasurementCollector {
+
+    SingleInvocationMeasurementCollector(
+        int measurementsPerTrial,
+        ShortDuration warmup,
+        ShortDuration maxWarmupWallTime,
+        ShortDuration nanoTimeGranularity) {
+      super(measurementsPerTrial, warmup, maxWarmupWallTime, nanoTimeGranularity);
+    }
+
+    @Override
+    void gcWhileMeasuring() {
+      messages.add("WARNING: GC occurred during timing. "
+          + "Depending on the scope of the benchmark, this might significantly impact results. "
+          + "Consider running with a larger heap size.");
+    }
+
+    @Override
+    void hotspotWhileMeasuring() {
+      messages.add("WARNING: Hotspot compilation occurred during timing. "
+          + "Depending on the scope of the benchmark, this might significantly impact results. "
+          + "Consider running with a longer warmup.");
+    }
+
+    @Override
+    void hotspotWhileNotMeasuring() {
+      messages.add(
+          "WARNING: Hotspot compilation occurred after warmup, but outside of timing. "
+              + "Depending on the scope of the benchmark, this might significantly impact results. "
+              + "Consider running with a longer warmup.");
+    }
+
+    @Override
+    void validateMeasurement(Measurement measurement) {
+      double nanos = measurement.value().magnitude() / measurement.weight();
+      if ((nanos / 1000) < nanoTimeGranularity.to(NANOSECONDS)) {
+        ShortDuration runtime = ShortDuration.of(BigDecimal.valueOf(nanos), NANOSECONDS);
+        throw new TrialFailureException(String.format(
+            "This experiment requires a microbenchmark. "
+            + "The granularity of the timer (%s) "
+            + "is greater than 0.1%% of the measured runtime (%s). "
+            + "Use the microbenchmark instrument for accurate measurements.",
+            nanoTimeGranularity, runtime));
+      }
+    }
+  }
+}
+
diff --git a/caliper/src/main/java/com/google/caliper/runner/RuntimeShutdownHookRegistrar.java b/caliper/src/main/java/com/google/caliper/runner/RuntimeShutdownHookRegistrar.java
new file mode 100644
index 0000000..9caea04
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/RuntimeShutdownHookRegistrar.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+/**
+ * A {@link ShutdownHookRegistrar} that delegates to {@link Runtime}.
+ */
+class RuntimeShutdownHookRegistrar implements ShutdownHookRegistrar {
+  @Override public void addShutdownHook(Thread hook) {
+    Runtime.getRuntime().addShutdownHook(hook);
+  }
+  
+  @Override public boolean removeShutdownHook(Thread hook) {
+    return Runtime.getRuntime().removeShutdownHook(hook);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ScheduledTrial.java b/caliper/src/main/java/com/google/caliper/runner/ScheduledTrial.java
new file mode 100644
index 0000000..fda696b
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ScheduledTrial.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 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.runner;
+
+import java.util.concurrent.Callable;
+
+import javax.inject.Inject;
+
+/**
+ * A ScheduledTrial is a simple pair of a {@link TrialRunLoop} and a 
+ * {@link TrialSchedulingPolicy}.
+ */
+@TrialScoped final class ScheduledTrial {
+  private final TrialRunLoop runLoop;
+  private final Experiment experiment;
+  private final TrialSchedulingPolicy policy;
+
+  @Inject ScheduledTrial(Experiment experiment, TrialRunLoop runLoop, 
+      TrialSchedulingPolicy policy) {
+    this.runLoop = runLoop;
+    this.experiment = experiment;
+    this.policy = policy;
+  }
+  
+  TrialSchedulingPolicy policy() {
+    return policy;
+  }
+  
+  Experiment experiment() {
+    return experiment;
+  }
+  
+  Callable<TrialResult> trialTask() {
+    return runLoop;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ServerSocketService.java b/caliper/src/main/java/com/google/caliper/runner/ServerSocketService.java
new file mode 100644
index 0000000..ed93dd5
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ServerSocketService.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.caliper.bridge.OpenedSocket;
+import com.google.caliper.bridge.StartupAnnounceMessage;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.AbstractExecutionThreadService;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.Service;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * A {@link Service} that manages a {@link ServerSocket}.
+ *
+ * <p> This service provides two pieces of functionality:
+ * <ol>
+ *   <li>It adapts {@link ServerSocket#accept()} to a {@link ListenableFuture} of an opened socket.
+ *   <li>It demultiplexes incoming connections based on a {@link StartupAnnounceMessage} that is
+ *       sent over the socket.
+ * </ol>
+ *
+ * <p>The {@linkplain State states} of this service are as follows:
+ * <ul>
+ *   <li>{@linkplain State#NEW NEW} : Idle state, the {@link ServerSocket} is not open yet.
+ *   <li>{@linkplain State#STARTING STARTING} : {@link ServerSocket} is opened
+ *   <li>{@linkplain State#RUNNING RUNNING} : We are continuously accepting and parsing connections
+ *       from the socket.
+ *   <li>{@linkplain State#STOPPING STOPPING} : The server socket is closing and all pending
+ *       connection requests are terminated, connection requests will fail immediately.
+ *   <li>{@linkplain State#TERMINATED TERMINATED} : Idle state, the socket is closed.
+ *   <li>{@linkplain State#FAILED FAILED} : The service will transition to failed if it encounters
+ *       any errors while accepting connections or reading from connections.
+ * </ul>
+ * 
+ * <p>Note to future self.  There have been a few attempts to make it so that it is no longer 
+ * necessary to dedicate a thread to this service (basically turn it into an AbstractIdleService).
+ * The general idea has been to make callers to getConnection invoke accept, here is why it didn't
+ * work.
+ * <ul>
+ *     <li>If you make getConnection a blocking method that calls accept until it finds the 
+ *         connection with its id, then there is no way to deal with connections that never arrive.
+ *         For example, if the worker crashes before connecting then the thread calling accept will
+ *         block forever waiting for it.  The only way to unblock a thread stuck on accept() is to
+ *         close the socket (this holds for ServerSocketChannels and normal ServerSockets), but we
+ *         cannot do that in this case because the socket is a shared resource.
+ *     <li>If you make getConnection a non-blocking, polling based method then you expose yourself
+ *         to potential deadlocks (due to missed signals) depending on what thread you poll from.
+ *         If the polling thread is any of the threads that are involved with processing messages
+ *         from the worker I believe there to be a deadlock risk.  Basically, if the worker sends
+ *         messages over its output streams and then calls Socket.connect, and no printing to stdout
+ *         or stderr occurs while connecting. Then if the runner polls, but misses the connection
+ *         and then tries to read again, it will deadlock.
+ * </ul>
+ */
+@Singleton
+final class ServerSocketService extends AbstractExecutionThreadService {
+  private enum Source { REQUEST, ACCEPT}
+  
+  private final Lock lock = new ReentrantLock();
+  
+  /**
+   * Contains futures that have either only been accepted or requested.  Once both occur they are
+   * removed from this map.
+   */
+  @GuardedBy("lock")
+  private final Map<UUID, SettableFuture<OpenedSocket>> halfFinishedConnections = Maps.newHashMap();
+  
+  /**
+   * Contains the history of connections so we can ensure that each id is only accepted once and
+   * requested once.
+   */
+  @GuardedBy("lock")
+  private final SetMultimap<Source, UUID> connectionState = Multimaps.newSetMultimap(
+      Maps.<Source, Collection<UUID>>newEnumMap(Source.class), 
+      new Supplier<Set<UUID>>(){
+        @Override public Set<UUID> get() {
+          return Sets.newHashSet();
+        }
+      });
+  
+  private ServerSocket serverSocket;
+
+  @Inject ServerSocketService() {}
+
+  int getPort() {
+    awaitRunning();
+    checkState(serverSocket != null, "Socket has not been opened yet");
+    return serverSocket.getLocalPort();
+  }
+
+  /**
+   * Returns a {@link ListenableFuture} for an open connection corresponding to the given id.
+   *
+   * <p>N.B. calling this method 'consumes' the connection and as such calling it twice with the
+   * same id will not work, the second future returned will never complete.  Similarly calling it
+   * with an id that does not correspond to a worker trying to connect will also fail.
+   */
+  public ListenableFuture<OpenedSocket> getConnection(UUID id) {
+    checkState(isRunning(), "You can only get connections from a running service: %s", this);
+    return getConnectionImpl(id, Source.REQUEST);
+  }
+
+  @Override protected void startUp() throws Exception {
+    serverSocket = new ServerSocket(0 /* bind to any available port */);
+  }
+
+  @Override protected void run() throws Exception {
+    while (isRunning()) {
+      Socket socket;
+      try {
+        socket = serverSocket.accept();
+      } catch (SocketException e) {
+        // we were closed
+        return;
+      }
+      OpenedSocket openedSocket = OpenedSocket.fromSocket(socket);
+
+      UUID id = ((StartupAnnounceMessage) openedSocket.reader().read()).trialId();
+      // N.B. you should not call set with the lock held, to prevent same thread executors from
+      // running with the lock.
+      getConnectionImpl(id, Source.ACCEPT).set(openedSocket);
+    }
+  }
+
+  /**
+   * Returns a {@link SettableFuture} from the map of connections.
+   * 
+   * <p>This method has the following properties:
+   * <ul>
+   *    <li>If the id is present in {@link #connectionState}, this will throw an 
+   *        {@link IllegalStateException}.
+   *    <li>The id and source are recorded in {@link #connectionState}
+   *    <li>If the future is already in {@link #halfFinishedConnections}, it is removed and 
+   *        returned.
+   *    <li>If the future is not in {@link #halfFinishedConnections}, a new {@link SettableFuture} 
+   *        is added and then returned.
+   * 
+   * <p>These features together ensure that each connection can only be accepted once, only 
+   * requested once and once both have happened it will be removed from 
+   * {@link #halfFinishedConnections}.
+   */
+  private SettableFuture<OpenedSocket> getConnectionImpl(UUID id, Source source) {
+    lock.lock();
+    try {
+      checkState(connectionState.put(source, id), "Connection for %s has already been %s",
+          id, source);
+      SettableFuture<OpenedSocket> future = halfFinishedConnections.get(id);
+      if (future == null) {
+        future = SettableFuture.create();
+        halfFinishedConnections.put(id, future);
+      } else {
+        halfFinishedConnections.remove(id);
+      }
+      return future;
+    } finally {
+      lock.unlock();
+    }
+  }
+
+  @Override protected void triggerShutdown() {
+    try {
+      serverSocket.close();
+    } catch (IOException e) {
+      // best effort...
+    }
+  }
+
+  @Override protected void shutDown() throws Exception {
+    serverSocket.close();
+    // Now we have either been asked to stop or have failed with some kind of exception, we want to
+    // notify all pending requests, so if there are any references outside of this class they will
+    // notice.
+    lock.lock();
+    try {
+      for (SettableFuture<OpenedSocket> future : halfFinishedConnections.values()) {
+        future.setException(new Exception("The socket has been closed"));
+      }
+      halfFinishedConnections.clear();
+      connectionState.clear();
+    } finally {
+      lock.unlock();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ServiceModule.java b/caliper/src/main/java/com/google/caliper/runner/ServiceModule.java
new file mode 100644
index 0000000..92f179d
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ServiceModule.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import com.google.common.util.concurrent.Service;
+import com.google.common.util.concurrent.ServiceManager;
+import dagger.Module;
+import dagger.Provides;
+
+import java.util.Set;
+import javax.inject.Singleton;
+
+/** Configures the {@link ServiceManager}. */
+@Module
+final class ServiceModule {
+  @Provides
+  @Singleton
+  static ServiceManager provideServiceManager(Set<Service> services) {
+    return new ServiceManager(services);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/ShutdownHookRegistrar.java b/caliper/src/main/java/com/google/caliper/runner/ShutdownHookRegistrar.java
new file mode 100644
index 0000000..b0f8a75
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/ShutdownHookRegistrar.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+/**
+ * A simple interface for registering and deregistering shutdown hooks.
+ */
+interface ShutdownHookRegistrar {
+  /**
+   * Adds a hook to run at process shutdown.
+   * 
+   * <p>See {@link Runtime#addShutdownHook(Thread)}.
+   */
+  void addShutdownHook(Thread hook);
+  /**
+   * Removes a shutdown hook that was previously registered via {@link #addShutdownHook(Thread)}.
+   * 
+   * <p>See {@link Runtime#removeShutdownHook(Thread)}.
+   */
+  boolean removeShutdownHook(Thread hook);
+}
+
diff --git a/caliper/src/main/java/com/google/caliper/runner/StreamService.java b/caliper/src/main/java/com/google/caliper/runner/StreamService.java
new file mode 100644
index 0000000..a5852d0
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/StreamService.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.caliper.bridge.LogMessage;
+import com.google.caliper.bridge.OpenedSocket;
+import com.google.caliper.bridge.StopMeasurementLogMessage;
+import com.google.caliper.model.Measurement;
+import com.google.caliper.runner.StreamService.StreamItem.Kind;
+import com.google.caliper.util.Parser;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.collect.Queues;
+import com.google.common.io.Closeables;
+import com.google.common.io.LineReader;
+import com.google.common.util.concurrent.AbstractService;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.Service; // for javadoc
+import com.google.common.util.concurrent.Service.State; // for javadoc
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.common.util.concurrent.Uninterruptibles;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Serializable;
+import java.nio.charset.Charset;
+import java.text.ParseException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+/**
+ * A {@link Service} that establishes a connection over a socket to a process and then allows 
+ * multiplexed access to the processes' line oriented output over the socket and the standard 
+ * process streams (stdout and stderr) as well as allowing data to be written over the socket.
+ * 
+ * <p>The {@linkplain State states} of this service are as follows:
+ * <ul>
+ *   <li>{@linkplain State#NEW NEW} : Idle state, no reading or writing is allowed.
+ *   <li>{@linkplain State#STARTING STARTING} : Streams are being opened
+ *   <li>{@linkplain State#RUNNING RUNNING} : At least one stream is still open or the writer has 
+ *       not been closed yet.
+ *   <li>{@linkplain State#STOPPING STOPPING} : All streams have closed but some threads may still 
+ *       be running.
+ *   <li>{@linkplain State#TERMINATED TERMINATED} : Idle state, all streams are closed
+ *   <li>{@linkplain State#FAILED FAILED} : The service will transition to failed if it encounters
+ *       any errors while reading from or writing to the streams, service failure will also cause 
+ *       the worker process to be forcibly shutdown and {@link #readItem(long, TimeUnit)}, 
+ *       {@link #closeWriter()} and {@link #sendMessage(Serializable)} will start throwing 
+ *       IllegalStateExceptions. 
+ * </ul>
+ */
+@TrialScoped final class StreamService extends AbstractService {
+  /** How long to wait for a process that should be exiting to actually exit. */
+  private static final int SHUTDOWN_WAIT_MILLIS = 10;
+
+  private static final Logger logger = Logger.getLogger(StreamService.class.getName());
+  private static final StreamItem TIMEOUT_ITEM = new StreamItem(Kind.TIMEOUT, null);
+
+  /** The final item that will be sent down the stream. */
+  static final StreamItem EOF_ITEM = new StreamItem(Kind.EOF, null);
+
+  private final ListeningExecutorService streamExecutor = MoreExecutors.listeningDecorator(
+      Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true).build()));
+  private final BlockingQueue<StreamItem> outputQueue = Queues.newLinkedBlockingQueue();
+  private final WorkerProcess worker;
+  private volatile Process process;
+  private final Parser<LogMessage> logMessageParser;
+  private final TrialOutputLogger trialOutput;
+  
+  /** 
+   * This represents the number of open streams from the users perspective.  i.e. can you still
+   * write to the socket and read items.
+   * 
+   * <p>This is decremented when either the socket is closed for writing or the EOF_ITEM has been
+   * read by the user.
+   */
+  private final AtomicInteger openStreams = new AtomicInteger();
+
+  /** 
+   * Used to track how many read streams are open so we can correctly set the EOF_ITEM onto the 
+   * queue.
+   */
+  private final AtomicInteger runningReadStreams = new AtomicInteger();
+  private OpenedSocket.Writer socketWriter;
+
+  @Inject StreamService(WorkerProcess worker,
+      Parser<LogMessage> logMessageParser, 
+      TrialOutputLogger trialOutput) {
+    this.worker = worker;
+    this.logMessageParser = logMessageParser;
+    this.trialOutput = trialOutput;
+  }
+
+  @Override protected void doStart() {
+    try {
+      // TODO(lukes): write the commandline to the trial output file?
+      process = worker.startWorker();
+    } catch (IOException e) {
+      notifyFailed(e);
+      return;
+    }
+    // Failsafe kill the process and the executor service.
+    // If the process has already exited cleanly, this will be a no-op.
+    addListener(new Listener() {
+      @Override public void starting() {}
+      @Override public void running() {}
+      @Override public void stopping(State from) {}
+      @Override public void terminated(State from) {
+        cleanup();
+      }
+      @Override public void failed(State from, Throwable failure) {
+        cleanup();
+      }
+
+      void cleanup() {
+        streamExecutor.shutdown();
+        process.destroy();
+        try {
+          streamExecutor.awaitTermination(10, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+        }
+        streamExecutor.shutdownNow();
+      }
+    }, MoreExecutors.directExecutor());
+    // You may be thinking as you read this "Yo dawg, what if IOExceptions rain from the sky?" 
+    // If a stream we are reading from throws an IOException then we fail the entire Service. This
+    // will cause the worker to be killed (if its not dead already) and the various StreamReaders to
+    // be interrupted (eventually).
+
+    // use the default charset because worker streams will use the default for output
+    Charset processCharset = Charset.defaultCharset();
+    runningReadStreams.addAndGet(2);
+    openStreams.addAndGet(1);
+    streamExecutor.submit(
+        threadRenaming("worker-stderr", 
+            new StreamReader("stderr", 
+                new InputStreamReader(process.getErrorStream(), processCharset))));
+    streamExecutor.submit(
+        threadRenaming("worker-stdout",
+            new StreamReader("stdout", 
+                new InputStreamReader(process.getInputStream(), processCharset))));
+    worker.socketFuture().addListener(
+        new Runnable() {
+          @Override public void run() {
+            try {
+              OpenedSocket openedSocket =
+                  Uninterruptibles.getUninterruptibly(worker.socketFuture());
+              logger.fine("successfully opened the pipe from the worker");
+              socketWriter = openedSocket.writer();
+              runningReadStreams.addAndGet(1);
+              openStreams.addAndGet(1);
+              streamExecutor.submit(threadRenaming("worker-socket",
+                  new SocketStreamReader(openedSocket.reader())));
+            } catch (ExecutionException e) {
+              notifyFailed(e.getCause());
+            }
+          }
+        },
+        MoreExecutors.directExecutor());
+    notifyStarted();
+  }
+  
+  /**
+   * Reads a {@link StreamItem} from one of the streams waiting for one to become available if 
+   * necessary.
+   */
+  StreamItem readItem(long timeout, TimeUnit unit) throws InterruptedException {
+    checkState(isRunning(), "Cannot read items from a %s StreamService", state());
+    StreamItem line = outputQueue.poll(timeout, unit);
+    if (line == EOF_ITEM) {
+      closeStream();
+    }
+    return (line == null) ? TIMEOUT_ITEM : line;
+  }
+  
+  /**
+   * Write a line of data to the worker process over the socket.
+   *
+   * <p>N.B. Writing data via {@link #sendMessage(Serializable)} is only valid once the underlying
+   * socket has been opened.  This should be fine assuming that socket writes are only in response
+   * to socket reads (which is currently the case), so there is no way that a write could happen
+   * prior to the socket being opened.
+  */
+  void sendMessage(Serializable message) throws IOException {
+    checkState(isRunning(), "Cannot read items from a %s StreamService", state());
+    checkState(socketWriter != null, "Attempted to write to the socket before it was opened.");
+    try {
+      socketWriter.write(message);
+      // We need to flush since this is a back and forth lockstep protocol, buffering can cause 
+      // deadlock! 
+      socketWriter.flush();
+    } catch (IOException e) {
+      Closeables.close(socketWriter, true);
+      notifyFailed(e);
+      throw e;
+    }
+  }
+  
+  /** Closes the socket writer. */
+  void closeWriter() throws IOException {
+    checkState(isRunning(), "Cannot read items from a %s StreamService", state());
+    checkState(socketWriter != null, "Attempted to close the socket before it was opened.");
+    try {
+      socketWriter.close();
+    } catch (IOException e) {
+      notifyFailed(e);
+      throw e;
+    }
+    closeStream();
+  }
+  
+  @Override protected void doStop() {
+    if (openStreams.get() > 0) {
+      // This means stop was called on us externally and we are still reading/writing, just log a
+      // warning and do nothing
+      logger.warning("Attempting to stop the stream service with streams still open");
+    }
+    final ListenableFuture<Integer> processFuture = streamExecutor.submit(new Callable<Integer>() {
+      @Override public Integer call() throws Exception {
+        return process.waitFor();
+      }
+    });
+    // Experimentally, even with well behaved processes there is some time between when all streams
+    // are closed as part of process shutdown and when the process has exited. So to not fail 
+    // flakily when shutting down normally we need to do a timed wait
+    streamExecutor.submit(new Callable<Void>() {
+      @Override public Void call() throws Exception {
+        boolean threw = true;
+        try {
+          if (processFuture.get(SHUTDOWN_WAIT_MILLIS, TimeUnit.MILLISECONDS) == 0) {
+            notifyStopped();
+          } else {
+            notifyFailed(
+                new Exception("Process failed to stop cleanly. Exit code: " + process.waitFor()));
+          }
+          threw = false;
+        } finally {
+          processFuture.cancel(true);  // we don't need it anymore
+          if (threw) {
+            process.destroy();
+            notifyFailed(
+                new Exception("Process failed to stop cleanly and was forcibly killed. Exit code: " 
+                    + process.waitFor()));
+          }
+        }
+        return null;
+      }
+    });
+  }
+  
+  private void closeStream() {
+    if (openStreams.decrementAndGet() == 0) {
+      stopAsync();
+    }
+  }
+  
+  private void closeReadStream() {
+    if (runningReadStreams.decrementAndGet() == 0) {
+      outputQueue.add(EOF_ITEM);
+    }
+  }
+
+  /** An item read from one of the streams. */
+  static class StreamItem {
+    enum Kind {
+      /** This indicates that it is the last item. */
+      EOF,
+      /** This indicates that reading the item timed out. */
+      TIMEOUT,
+      /** This indicates that this item has content. */
+      DATA;
+    }
+    
+    @Nullable private final LogMessage logMessage;
+    private final Kind kind;
+    
+    private StreamItem(LogMessage line) {
+      this(Kind.DATA, checkNotNull(line));
+    }
+    
+    private StreamItem(Kind state, @Nullable LogMessage logMessage) {
+      this.logMessage = logMessage;
+      this.kind = state;
+    }
+    
+    /** Returns the content.  This is only valid if {@link #kind()} return {@link Kind#DATA}. */
+    LogMessage content() {
+      checkState(kind == Kind.DATA, "Only data lines have content: %s", this);
+      return logMessage;
+    }
+    
+    Kind kind() {
+      return kind;
+    }
+    
+    @Override public String toString() {
+      ToStringHelper helper = MoreObjects.toStringHelper(StreamItem.class);
+      if (kind == Kind.DATA) {
+        helper.addValue(logMessage);
+      } else {
+        helper.addValue(kind);
+      }
+      return helper.toString();
+    }
+  }
+  
+  /** Returns a callable that renames the the thread that the given callable runs in. */
+  private static <T> Callable<T> threadRenaming(final String name, final Callable<T> callable) {
+    checkNotNull(name);
+    checkNotNull(callable);
+    return new Callable<T>() {
+      @Override public T call() throws Exception {
+        Thread currentThread = Thread.currentThread();
+        String oldName = currentThread.getName();
+        currentThread.setName(name);
+        try {
+          return callable.call();
+        } finally {
+          currentThread.setName(oldName);
+        }
+      }
+    };
+  }
+
+  /**
+   * A background task that reads lines of text from a {@link Reader} and puts them onto a 
+   * {@link BlockingQueue}.
+   */
+  private final class StreamReader implements Callable<Void> {
+    final Reader reader;
+    final String streamName;
+
+    StreamReader(String streamName, Reader reader) {
+      this.streamName = streamName;
+      this.reader = reader;
+    }
+    
+    @Override public Void call() throws IOException, InterruptedException, ParseException {
+      LineReader lineReader = new LineReader(reader);
+      boolean threw = true;
+      try {
+        String line;
+        while ((line = lineReader.readLine()) != null) {
+          trialOutput.log(streamName, line);
+          LogMessage logMessage = logMessageParser.parse(line);
+          if (logMessage != null) {
+            outputQueue.put(new StreamItem(logMessage));
+          }
+        }
+        threw = false;
+      } catch (Exception e) {
+        notifyFailed(e);
+      } finally {
+        closeReadStream();
+        Closeables.close(reader, threw);
+      }
+      return null;
+    }
+  }
+  
+  /**
+   * A background task that reads lines of text from a {@link OpenedSocket.Reader} and puts them
+   * onto a {@link BlockingQueue}.
+   */
+  private final class SocketStreamReader implements Callable<Void> {
+    final OpenedSocket.Reader reader;
+
+    SocketStreamReader(OpenedSocket.Reader reader) {
+      this.reader = reader;
+    }
+    
+    @Override public Void call() throws IOException, InterruptedException, ParseException {
+      boolean threw = true;
+      try {
+        Object obj;
+        while ((obj = reader.read()) != null) {
+          if (obj instanceof String) {
+            log(obj.toString());
+            continue;
+          } 
+          LogMessage message = (LogMessage) obj;
+          if (message instanceof StopMeasurementLogMessage) {
+            // TODO(lukes): how useful are these messages?  They seem like leftover debugging info
+            for (Measurement measurement : ((StopMeasurementLogMessage) message).measurements()) {
+              log(String.format("I got a result! %s: %f%s%n",
+                  measurement.description(),
+                  measurement.value().magnitude() / measurement.weight(), 
+                  measurement.value().unit()));
+            }
+          }
+          outputQueue.put(new StreamItem(message));
+        }
+        threw = false;
+      } catch (Exception e) {
+        notifyFailed(e);
+      } finally {
+        closeReadStream();
+        Closeables.close(reader, threw);
+      }
+      return null;
+    }
+
+    private void log(String text) {
+      trialOutput.log("socket", text);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialFailureException.java b/caliper/src/main/java/com/google/caliper/runner/TrialFailureException.java
new file mode 100644
index 0000000..923750b
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialFailureException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+/**
+ * An exception representing the failure of an individual trial. Throwing this exception will
+ * invalidate the trial, but allow the run to continue. Both the runner and individual instruments
+ * are free to throw this exception.
+ *
+ * <p>The exception message is used to convey the nature of the failure to the user.
+ */
+final class TrialFailureException extends RuntimeException {
+  public TrialFailureException(String message) {
+    super(message);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialId.java b/caliper/src/main/java/com/google/caliper/runner/TrialId.java
new file mode 100644
index 0000000..9a7c6fe
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialId.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/** Binding annotation for the current trial id. */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+@interface TrialId {}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialModule.java b/caliper/src/main/java/com/google/caliper/runner/TrialModule.java
new file mode 100644
index 0000000..73d0044
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialModule.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.caliper.bridge.LogMessage;
+import com.google.caliper.bridge.OpenedSocket;
+import com.google.caliper.model.BenchmarkSpec;
+import com.google.caliper.model.Host;
+import com.google.caliper.model.Run;
+import com.google.caliper.model.Scenario;
+import com.google.caliper.model.Trial;
+import com.google.caliper.runner.Instrument.MeasurementCollectingVisitor;
+import com.google.caliper.util.Parser;
+import com.google.common.util.concurrent.ListenableFuture;
+import dagger.Module;
+import dagger.Provides;
+
+import java.util.UUID;
+
+/**
+ * Configuration for a {@link TrialRunLoop}.
+ */
+@Module
+final class TrialModule {
+
+  private final UUID trialId;
+  private final int trialNumber;
+  private final Experiment experiment;
+
+  TrialModule(UUID trialId, int trialNumber, Experiment experiment) {
+    this.trialId = trialId;
+    this.trialNumber = trialNumber;
+    this.experiment = experiment;
+  }
+
+  @TrialScoped
+  @Provides
+  @TrialId
+  UUID provideTrialId() {
+    return trialId;
+  }
+
+  @TrialScoped
+  @Provides
+  @TrialNumber
+  int provideTrialNumber() {
+    return trialNumber;
+  }
+
+  @TrialScoped
+  @Provides
+  Experiment provideExperiment() {
+    return experiment;
+  }
+
+  @TrialScoped
+  @Provides
+  static BenchmarkSpec provideBenchmarkSpec(Experiment experiment) {
+    return new BenchmarkSpec.Builder()
+        .className(experiment.instrumentation().benchmarkMethod().getDeclaringClass().getName())
+        .methodName(experiment.instrumentation().benchmarkMethod().getName())
+        .addAllParameters(experiment.userParameters())
+        .build();
+  }
+
+  @Provides
+  @TrialScoped
+  static ListenableFuture<OpenedSocket> provideTrialSocket(
+      @TrialId UUID trialId,
+      ServerSocketService serverSocketService) {
+    return serverSocketService.getConnection(trialId);
+  }
+
+  @Provides
+  static MeasurementCollectingVisitor provideMeasurementCollectingVisitor(Experiment experiment) {
+    return experiment.instrumentation().getMeasurementCollectingVisitor();
+  }
+
+  @Provides
+  @TrialScoped
+  static TrialSchedulingPolicy provideTrialSchedulingPolicy(Experiment experiment) {
+    return experiment.instrumentation().instrument().schedulingPolicy();
+  }
+
+  @Provides
+  @TrialScoped
+  static StreamService provideStreamService(
+      WorkerProcess worker,
+      Parser<LogMessage> logMessageParser,
+      TrialOutputLogger trialOutput) {
+    return new StreamService(worker, logMessageParser, trialOutput);
+  }
+
+  // TODO(user): make this a singleton in a higher level module.
+  @Provides
+  @TrialScoped
+  static ShutdownHookRegistrar provideShutdownHook() {
+    return new RuntimeShutdownHookRegistrar();
+  }
+
+  @Provides static TrialResultFactory provideTrialFactory(
+      @TrialId final UUID trialId,
+      final Run run,
+      final Host host,
+      final Experiment experiment,
+      final BenchmarkSpec benchmarkSpec) {
+    return new TrialResultFactory() {
+      @Override public TrialResult newTrialResult(
+          VmDataCollectingVisitor dataCollectingVisitor,
+          MeasurementCollectingVisitor measurementCollectingVisitor) {
+        checkState(measurementCollectingVisitor.isDoneCollecting());
+        // TODO(lukes): should the trial messages be part of the Trial datastructure?  It seems like
+        // the web UI could make use of them.
+        return new TrialResult(
+            new Trial.Builder(trialId)
+                .run(run)
+                .instrumentSpec(experiment.instrumentation().instrument().getSpec())
+                .scenario(new Scenario.Builder()
+                    .host(host)
+                    .vmSpec(dataCollectingVisitor.vmSpec())
+                    .benchmarkSpec(benchmarkSpec))
+                .addAllMeasurements(measurementCollectingVisitor.getMeasurements())
+                .build(),
+            experiment,
+            measurementCollectingVisitor.getMessages());
+      }
+    };
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialNumber.java b/caliper/src/main/java/com/google/caliper/runner/TrialNumber.java
new file mode 100644
index 0000000..1437749
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialNumber.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/** Binding annotation the trial number, used for debugging purposes. */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+@interface TrialNumber {}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialOutputFactory.java b/caliper/src/main/java/com/google/caliper/runner/TrialOutputFactory.java
new file mode 100644
index 0000000..588a330
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialOutputFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 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.runner;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+
+/** 
+ * A factory for trial log files.
+ * 
+ * <p>The log files may be configured to be deleted on exit of the runner process.  If the files
+ * should not be deleted then call {@link #persistFile(File)} to ensure that they survive.
+ */
+interface TrialOutputFactory {
+  
+  /** A simple tuple of a {@link File} and a {@link PrintWriter} for writing to that file. */
+  final class FileAndWriter {
+    final File file;
+    final PrintWriter writer;
+
+    FileAndWriter(File file, PrintWriter writer) {
+      this.file = file;
+      this.writer = writer;
+    }
+  }
+
+  /** Returns the file to write trial output to. */
+  FileAndWriter getTrialOutputFile(int trialNumber) throws FileNotFoundException;
+
+  /** 
+   * Ensures that the given file will not be deleted after the run.  The file provided must be equal
+   * to a file returned by {@link #getTrialOutputFile(int)}.
+   */
+  void persistFile(File f);
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialOutputFactoryService.java b/caliper/src/main/java/com/google/caliper/runner/TrialOutputFactoryService.java
new file mode 100644
index 0000000..31175dd
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialOutputFactoryService.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 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.runner;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.caliper.model.Run;
+import com.google.caliper.options.CaliperOptions;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import com.google.common.util.concurrent.AbstractIdleService;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * A {@link TrialOutputFactory} implemented as a service that manages a directory either under 
+ * {@code /tmp} or in a user configured directory.
+ * 
+ * <p>If there is a user configured directory, then no files will be deleted on service shutdown.
+ * Otherwise the only way to ensure that the log files survive service shutdown is to explicitly
+ * call {@link #persistFile(File)} with each file that should not be deleted.
+ */
+@Singleton
+final class TrialOutputFactoryService
+    extends AbstractIdleService implements TrialOutputFactory {
+  private static final String LOG_DIRECTORY_PROPERTY = "worker.output";
+
+  private final CaliperOptions options;
+  private final Run run;
+
+  @GuardedBy("this")
+  private final Set<String> toDelete = new LinkedHashSet<String>();
+
+  @GuardedBy("this")
+  private File directory;
+
+  @GuardedBy("this")
+  private boolean persistFiles;
+
+  @Inject TrialOutputFactoryService(Run run, CaliperOptions options) {
+    this.run = run;
+    this.options = options;
+  }
+
+  /** Returns the file to write trial output to. */
+  @Override public FileAndWriter getTrialOutputFile(int trialNumber) throws FileNotFoundException {
+    File dir;
+    synchronized (this) {
+      if (directory == null) {
+        throw new RuntimeException(
+            String.format("The output manager %s has not been started yet", this));
+      }
+      dir = directory;
+    }
+    File trialFile = new File(dir, String.format("trial-%d.log", trialNumber));
+    synchronized (this) {
+      if (!persistFiles) {
+          toDelete.add(trialFile.getPath());
+      }
+    }
+    return new FileAndWriter(trialFile,
+        new PrintWriter(
+            new BufferedWriter(
+                new OutputStreamWriter(
+                    new FileOutputStream(trialFile), 
+                Charsets.UTF_8))));
+  }
+
+  /** 
+   * Ensures that the given file will not be deleted on exit of the JVM, possibly by copying to a 
+   * new file.
+   */
+  @Override public synchronized void persistFile(File f) {
+    if (!persistFiles) {
+      checkArgument(toDelete.remove(f.getPath()), "%s was not created by the output manager", f);
+    }
+  }
+
+  @Override protected synchronized void startUp() throws Exception {
+    File directory;
+    String dirName = options.configProperties().get(LOG_DIRECTORY_PROPERTY);
+    boolean persistFiles = true;
+    if (dirName != null) {
+      directory = new File(dirName);
+      if (!directory.exists()) {
+        if (!directory.mkdirs()) {
+          throw new Exception(
+              String.format("Unable to create directory %s indicated by property %s",
+                  dirName, LOG_DIRECTORY_PROPERTY));
+        }
+      } else if (!directory.isDirectory()) {
+        throw new Exception(
+            String.format("Configured directory %s indicated by property %s is not a directory",
+                dirName, LOG_DIRECTORY_PROPERTY));
+      }
+      // The directory exists and is a directory
+      directory = new File(directory, String.format("run-%s", run.id()));
+      if (!directory.mkdir()) {
+        throw new Exception("Unable to create a run directory " + directory);
+      }
+    } else {
+      // If none is configured then we don't care, just make a temp dir
+      // TODO(lukes): it would be nice to use jdk7 java.nio.file.Files.createTempDir() which allows
+      // us to specify a name, but caliper is still on jdk6.
+      directory = Files.createTempDir();
+      persistFiles = false;
+    }
+    this.directory = directory;
+    this.persistFiles = persistFiles;
+  }
+
+  @Override protected synchronized void shutDown() throws Exception {
+    if (!persistFiles) {
+      // This is best effort, files to be deleted are already in a tmp directory.
+      for (String f : toDelete) {
+        new File(f).delete();
+      }
+      // This will only succeed if the directory is empty which is what we want.
+      directory.delete();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialOutputLogger.java b/caliper/src/main/java/com/google/caliper/runner/TrialOutputLogger.java
new file mode 100644
index 0000000..1803fab
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialOutputLogger.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2014 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.runner;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.caliper.runner.TrialOutputFactory.FileAndWriter;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.UUID;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.inject.Inject;
+
+/**
+ * A logger to write trial output to a file.
+ */
+@TrialScoped final class TrialOutputLogger implements Closeable {
+  @GuardedBy("this")
+  private File file;
+
+  @GuardedBy("this")
+  private PrintWriter writer;
+
+  private final int trialNumber;
+  private final Experiment experiment;
+  private final UUID trialId;
+  private final TrialOutputFactory outputManager;
+
+  @Inject TrialOutputLogger(TrialOutputFactory outputManager, @TrialNumber int trialNumber,
+      @TrialId UUID trialId, Experiment experiment) {
+    this.outputManager = outputManager;
+    this.trialNumber = trialNumber;
+    this.trialId = trialId;
+    this.experiment = experiment;
+  }
+  
+  /** Opens the trial output file. */
+  synchronized void open() throws IOException {
+    if (writer == null) {
+      FileAndWriter fileAndWriter = outputManager.getTrialOutputFile(trialNumber);
+      file = fileAndWriter.file;
+      writer = fileAndWriter.writer;
+    }
+  }
+  
+  /** 
+   * Ensures that the writer has been opened. also creates a happens-before edge that ensures that
+   * writer is visible (and non-null) after a non-exceptional return from this method.
+   */
+  private synchronized void checkOpened() {
+    checkState(writer != null, "The logger is not open");
+  }
+
+  /** Prints header information to the file. */
+  synchronized void printHeader() {
+    checkOpened();
+    // make the file self describing
+    // TODO(lukes): we could print the command line here.  The user wouldn't be able to run it again
+    // since there would be no runner sending continue messages, but it might be useful to debug
+    // classpath issues.
+    writer.println("Trial Number: " + trialNumber);
+    writer.println("Trial Id: " + trialId);
+    writer.println("Experiment: " + experiment);
+    writer.println();
+  }
+  
+  /** 
+   * Logs a line of output to the logger.
+   * 
+   * @param source The source of the line (e.g. 'stderr')
+   * @param line The output
+   */
+  synchronized void log(String source, String line) {
+    checkOpened();
+    writer.printf("[%s] %s%n", source, line);
+  }
+  
+  @Override public synchronized void close() {
+    if (writer != null) {
+      writer.close();
+    }
+  }
+
+  /** Marks the log file so that it will not be deleted at the end of the benchmark. */
+  synchronized void ensureFileIsSaved() {
+    checkOpened();
+    outputManager.persistFile(file);
+  }
+  
+  /** Returns the log file path. */
+  synchronized File trialOutputFile() {
+    checkOpened();
+    return file;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialResult.java b/caliper/src/main/java/com/google/caliper/runner/TrialResult.java
new file mode 100644
index 0000000..a870b27
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialResult.java
@@ -0,0 +1,45 @@
+/*
+ * 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.runner;
+
+import com.google.caliper.model.Trial;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * A simple tuple of the data
+ */
+final class TrialResult {
+  private final Trial trial;
+  private final Experiment experiment;
+  private final ImmutableList<String> trialMessages;
+
+  TrialResult(Trial trial, Experiment experiment, ImmutableList<String> trialMessages) {
+    this.trial = trial;
+    this.experiment = experiment;
+    this.trialMessages = trialMessages;
+  }
+  
+  Experiment getExperiment() {
+    return experiment;
+  }
+
+  Trial getTrial() {
+    return trial;
+  }
+
+  ImmutableList<String> getTrialMessages() {
+    return trialMessages;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialResultFactory.java b/caliper/src/main/java/com/google/caliper/runner/TrialResultFactory.java
new file mode 100644
index 0000000..2f8e2a2
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialResultFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import com.google.caliper.model.Trial;
+import com.google.caliper.runner.Instrument.MeasurementCollectingVisitor;
+
+/** 
+ * A factory for producing {@link TrialResult TrialResults} based on data collected from visitors.
+ */
+interface TrialResultFactory {
+  /** Returns a new {@link Trial}. */
+  TrialResult newTrialResult(VmDataCollectingVisitor vmData, 
+      MeasurementCollectingVisitor measurementData);
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialRunLoop.java b/caliper/src/main/java/com/google/caliper/runner/TrialRunLoop.java
new file mode 100644
index 0000000..587f3d2
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialRunLoop.java
@@ -0,0 +1,199 @@
+/*
+ * 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.runner;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import com.google.caliper.bridge.LogMessage;
+import com.google.caliper.bridge.ShouldContinueMessage;
+import com.google.caliper.bridge.StopMeasurementLogMessage;
+import com.google.caliper.model.Trial;
+import com.google.caliper.options.CaliperOptions;
+import com.google.caliper.runner.Instrument.MeasurementCollectingVisitor;
+import com.google.caliper.runner.StreamService.StreamItem;
+import com.google.caliper.util.ShortDuration;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Throwables;
+import com.google.common.util.concurrent.Service.State;
+
+import org.joda.time.Duration;
+
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+/**
+ * The main data gather control loop for a Trial.
+ *
+ * <p>This class starts the worker process, reads all the data from it and constructs the
+ * {@link Trial} while enforcing the trial timeout.
+ */
+@TrialScoped class TrialRunLoop implements Callable<TrialResult> {
+  private static final Logger logger = Logger.getLogger(TrialRunLoop.class.getName());
+
+  /** The time that the worker has to clean up after an experiment. */
+  private static final Duration WORKER_CLEANUP_DURATION = Duration.standardSeconds(2);
+
+  private final CaliperOptions options;
+  private final StreamService streamService;
+  private final TrialResultFactory trialFactory;
+
+  // TODO(lukes): The VmDataCollectingVisitor should be able to tell us when it has collected all
+  // its data.
+  private final VmDataCollectingVisitor dataCollectingVisitor;
+  private final Stopwatch trialStopwatch = Stopwatch.createUnstarted();
+  private final MeasurementCollectingVisitor measurementCollectingVisitor;
+  private final TrialOutputLogger trialOutput;
+
+  @Inject TrialRunLoop(
+      MeasurementCollectingVisitor measurementCollectingVisitor,
+      CaliperOptions options,
+      TrialResultFactory trialFactory,
+      TrialOutputLogger trialOutput,
+      StreamService streamService,
+      VmDataCollectingVisitor dataCollectingVisitor) {
+    this.options = options;
+    this.trialFactory = trialFactory;
+    this.streamService = streamService;
+    this.measurementCollectingVisitor = measurementCollectingVisitor; 
+    this.trialOutput = trialOutput;
+    this.dataCollectingVisitor = dataCollectingVisitor;
+  }
+
+  @Override public TrialResult call() throws TrialFailureException, IOException {
+    if (streamService.state() != State.NEW) {
+      throw new IllegalStateException("You can only invoke the run loop once");
+    }
+    trialOutput.open();
+    trialOutput.printHeader();
+    streamService.startAsync().awaitRunning();
+    try {
+      long timeLimitNanos = getTrialTimeLimitTrialNanos();
+      boolean doneCollecting = false;
+      boolean done = false;
+      while (!done) {
+        StreamItem item;
+        try {
+          item = streamService.readItem(
+              timeLimitNanos - trialStopwatch.elapsed(NANOSECONDS), 
+              NANOSECONDS);
+        } catch (InterruptedException e) {
+          trialOutput.ensureFileIsSaved();
+          // Someone has asked us to stop (via Futures.cancel?).
+          if (doneCollecting) {
+            logger.log(Level.WARNING, "Trial cancelled before completing normally (but after "
+                + "collecting sufficient data). Inspect {0} to see any worker output", 
+                trialOutput.trialOutputFile());
+            done = true;
+            break;
+          }
+          // We were asked to stop but we didn't actually finish (the normal case).  Fail the trial.
+          throw new TrialFailureException(
+              String.format("Trial cancelled.  Inspect %s to see any worker output.",
+                trialOutput.trialOutputFile()));
+        }
+        switch (item.kind()) {
+          case DATA:
+            LogMessage logMessage = item.content();
+            logMessage.accept(measurementCollectingVisitor);
+            logMessage.accept(dataCollectingVisitor);
+            if (!doneCollecting && measurementCollectingVisitor.isDoneCollecting()) {
+              doneCollecting = true;
+              // We have received all the measurements we need and are about to tell the worker to
+              // shut down.  At this point the worker should shutdown soon, but we don't want to 
+              // wait too long, so decrease the time limit so that we wait no more than 
+              // WORKER_CLEANUP_DURATION.
+              long cleanupTimeNanos = MILLISECONDS.toNanos(WORKER_CLEANUP_DURATION.getMillis());
+              // TODO(lukes): Does the min operation make sense here? should we just use the 
+              // cleanupTimeNanos?
+              timeLimitNanos = trialStopwatch.elapsed(NANOSECONDS) + cleanupTimeNanos;
+            }
+            // If it is a stop measurement message we need to tell the worker to either stop or keep
+            // going with a WorkerContinueMessage.  This needs to be done after the 
+            // measurementCollecting visitor sees the message so that isDoneCollection will be up to
+            // date.
+            if (logMessage instanceof StopMeasurementLogMessage) {
+              // TODO(lukes): this is a blocking write, perhaps we should perform it in a non 
+              // blocking manner to keep this thread only blocking in one place.  This would 
+              // complicate error handling, but may increase performance since it would free this
+              // thread up to handle other messages
+              streamService.sendMessage(
+                  new ShouldContinueMessage(
+                      !doneCollecting,
+                      measurementCollectingVisitor.isWarmupComplete()));
+              if (doneCollecting) {
+                streamService.closeWriter();
+              }
+            }
+            break;
+          case EOF:
+            // We consider EOF to be synonymous with worker shutdown
+            if (!doneCollecting) {
+              trialOutput.ensureFileIsSaved();
+              throw new TrialFailureException(String.format("The worker exited without producing "
+                  + "data. It has likely crashed. Inspect %s to see any worker output.", 
+                  trialOutput.trialOutputFile()));
+            }
+            done = true;
+            break;
+          case TIMEOUT:
+            trialOutput.ensureFileIsSaved();
+            if (doneCollecting) {
+              // Should this be an error?
+              logger.log(Level.WARNING, "Worker failed to exit cleanly within the alloted time. "
+                  + "Inspect {0} to see any worker output", trialOutput.trialOutputFile());
+              done = true;
+            } else {
+              throw new TrialFailureException(String.format(
+                  "Trial exceeded the total allowable runtime (%s). "
+                      + "The limit may be adjusted using the --time-limit flag.  Inspect %s to "
+                      + "see any worker output",
+                      options.timeLimit(), trialOutput.trialOutputFile()));
+            }
+            break;
+          default:
+            throw new AssertionError("Impossible item: " + item);
+        }
+      }
+      return trialFactory.newTrialResult(dataCollectingVisitor, measurementCollectingVisitor);
+    } catch (Throwable e) {
+      Throwables.propagateIfInstanceOf(e, TrialFailureException.class);
+      // This is some failure that is not a TrialFailureException, let the exception propagate but
+      // log the filename for the user. 
+      trialOutput.ensureFileIsSaved();
+      logger.severe(
+          String.format(
+              "Unexpected error while executing trial. Inspect %s to see any worker output.", 
+              trialOutput.trialOutputFile()));
+      throw Throwables.propagate(e);
+    } finally {
+      trialStopwatch.reset();
+      streamService.stopAsync();
+      trialOutput.close();
+    }
+  }
+
+  private long getTrialTimeLimitTrialNanos() {
+    ShortDuration timeLimit = options.timeLimit();
+    if (ShortDuration.zero().equals(timeLimit)) {
+      return Long.MAX_VALUE;
+    }
+    return timeLimit.to(NANOSECONDS);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialSchedulingPolicy.java b/caliper/src/main/java/com/google/caliper/runner/TrialSchedulingPolicy.java
new file mode 100644
index 0000000..604ca9c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialSchedulingPolicy.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 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.runner;
+
+/**
+ * The scheduling policy for a particular trial.
+ *
+ * <p>TODO(lukes): Currently this is extremely simple.  Trials can be scheduled in parallel with
+ * other trials or not.  In the future, this should use some kind of cost modeling.
+ */
+enum TrialSchedulingPolicy {
+  PARALLEL,
+  SERIAL;
+}
diff --git a/caliper/src/main/java/com/google/caliper/UploadResults.java b/caliper/src/main/java/com/google/caliper/runner/TrialScopeComponent.java
similarity index 65%
copy from caliper/src/main/java/com/google/caliper/UploadResults.java
copy to caliper/src/main/java/com/google/caliper/runner/TrialScopeComponent.java
index 7dae7af..5f23549 100644
--- a/caliper/src/main/java/com/google/caliper/UploadResults.java
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialScopeComponent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2015 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
+package com.google.caliper.runner;
 
-import java.io.File;
+import dagger.Subcomponent;
 
 /**
- * Usage: UploadResults <file_or_dir>
+ * Component for creating {@link ScheduledTrial} in the {@link TrialScoped}.
  */
-public class UploadResults {
-  public static void main(String[] args) {
-    new Runner().uploadResultsFileOrDir(new File(args[0]));
-  }
+@TrialScoped
+@Subcomponent(modules = {TrialModule.class})
+interface TrialScopeComponent {
+  ScheduledTrial getScheduledTrial();
 }
diff --git a/caliper/src/main/java/com/google/caliper/runner/TrialScoped.java b/caliper/src/main/java/com/google/caliper/runner/TrialScoped.java
new file mode 100644
index 0000000..81a465a
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/TrialScoped.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for the TrialScope.
+ *
+ * <p>Apply this to binding for which there can only be one per trial.
+ */
+@Target({ TYPE, METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Scope
+@interface TrialScoped {}
diff --git a/caliper/src/main/java/com/google/caliper/runner/UserCodeException.java b/caliper/src/main/java/com/google/caliper/runner/UserCodeException.java
new file mode 100644
index 0000000..1aa6337
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/UserCodeException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.runner;
+
+import java.io.PrintWriter;
+
+/**
+ * Signifies that the user's benchmark code threw an exception.
+ */
+@SuppressWarnings("serial")
+public class UserCodeException extends InvalidBenchmarkException {
+  public UserCodeException(String message, Throwable cause) {
+    super(message);
+    initCause(cause);
+  }
+
+  public UserCodeException(Throwable cause) {
+    this("An exception was thrown from the benchmark code", cause);
+  }
+
+  @Override public void display(PrintWriter writer) {
+    printStackTrace(writer);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/VirtualMachine.java b/caliper/src/main/java/com/google/caliper/runner/VirtualMachine.java
new file mode 100644
index 0000000..f48020b
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/VirtualMachine.java
@@ -0,0 +1,54 @@
+/*
+ * 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.runner;
+
+import com.google.caliper.config.VmConfig;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+/**
+ * A named virtual machine configuration.
+ */
+final class VirtualMachine {
+  final String name;
+  final VmConfig config;
+
+  VirtualMachine(String name, VmConfig config) {
+    this.name = name;
+    this.config = config;
+  }
+
+  @Override public boolean equals(Object object) {
+    if (object instanceof VirtualMachine) {
+      VirtualMachine that = (VirtualMachine) object;
+      return this.name.equals(that.name)
+          && this.config.equals(that.config);
+    }
+    return false;
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(name, config);
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("name", name)
+        .add("config", config)
+        .toString();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/VmDataCollectingVisitor.java b/caliper/src/main/java/com/google/caliper/runner/VmDataCollectingVisitor.java
new file mode 100644
index 0000000..3b56d53
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/VmDataCollectingVisitor.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import com.google.caliper.bridge.AbstractLogMessageVisitor;
+import com.google.caliper.bridge.FailureLogMessage;
+import com.google.caliper.bridge.VmOptionLogMessage;
+import com.google.caliper.bridge.VmPropertiesLogMessage;
+import com.google.caliper.model.VmSpec;
+import com.google.caliper.platform.Platform;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+import javax.inject.Inject;
+
+/** An {@link AbstractLogMessageVisitor} that collects data about JVM properties and options. */
+@TrialScoped
+final class VmDataCollectingVisitor extends AbstractLogMessageVisitor {
+  private final ImmutableMap.Builder<String, String> vmOptionsBuilder = ImmutableMap.builder();
+  private final Platform platform;
+  private Optional<ImmutableMap<String, String>> vmProperties = Optional.absent();
+
+  @Inject VmDataCollectingVisitor(Platform platform) {
+    this.platform = platform;
+  }
+
+  /**
+   * Returns a {@link VmSpec} based on the data gathered by this visitor.
+   *
+   * @throws IllegalStateException if not all the data has been gathered.
+   */
+  VmSpec vmSpec() {
+    ImmutableMap<String, String> options = vmOptionsBuilder.build();
+    platform.checkVmProperties(options);
+    return new VmSpec.Builder()
+        .addAllProperties(vmProperties.get())
+        .addAllOptions(options)
+        .build();
+  }
+
+  @Override
+  public void visit(FailureLogMessage logMessage) {
+    throw new ProxyWorkerException(logMessage.stackTrace());
+  }
+
+  @Override
+  public void visit(VmOptionLogMessage logMessage) {
+    vmOptionsBuilder.put(logMessage.name(), logMessage.value());
+  }
+
+  @Override
+  public void visit(VmPropertiesLogMessage logMessage) {
+    vmProperties = Optional.of(ImmutableMap.copyOf(
+        Maps.filterKeys(logMessage.properties(), platform.vmPropertiesToRetain())));
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/runner/WorkerProcess.java b/caliper/src/main/java/com/google/caliper/runner/WorkerProcess.java
new file mode 100644
index 0000000..144a21c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/runner/WorkerProcess.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2012 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.runner;
+
+import com.google.caliper.bridge.CommandLineSerializer;
+import com.google.caliper.bridge.OpenedSocket;
+import com.google.caliper.bridge.WorkerSpec;
+import com.google.caliper.config.VmConfig;
+import com.google.caliper.model.BenchmarkSpec;
+import com.google.caliper.runner.Instrument.Instrumentation;
+import com.google.caliper.worker.WorkerMain;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.inject.Inject;
+
+/**
+ * A representation of an unstarted worker.
+ *
+ * <p>A worker is a sub process that runs a benchmark trial.  Specifically it is a JVM running
+ * {@link com.google.caliper.worker.WorkerMain}.  Because of this we can make certain assumptions
+ * about its behavior, including but not limited to:
+ *
+ * <ul>
+ *   <li>The worker will connect back to us over a socket connection and send us UTF-8 json
+ *       messages in a line oriented protocol.
+ *   <li>TODO(lukes,gak): This is probably as good a place as any to specify the entire protocol.
+ * </ul>
+ */
+@TrialScoped final class WorkerProcess {
+  private static final Logger logger = Logger.getLogger(WorkerProcess.class.getName());
+
+  @GuardedBy("this")
+  private Process worker;
+  private final ProcessBuilder workerBuilder;
+  private final ShutdownHookRegistrar shutdownHookRegistrar;
+  private final ListenableFuture<OpenedSocket> openedSocket;
+  private final UUID trialId;
+
+  @VisibleForTesting WorkerProcess(ProcessBuilder workerBuilder,
+      UUID trialId,
+      ListenableFuture<OpenedSocket> openedSocket,
+      ShutdownHookRegistrar shutdownHookRegistrar) {
+    this.trialId = trialId;
+    this.workerBuilder = workerBuilder;
+    this.openedSocket = openedSocket;
+    this.shutdownHookRegistrar = shutdownHookRegistrar;
+  }
+
+  @Inject WorkerProcess(@TrialId UUID trialId,
+      ListenableFuture<OpenedSocket> openedSocket,
+      Experiment experiment,
+      BenchmarkSpec benchmarkSpec,
+      @LocalPort int localPort,
+      BenchmarkClass benchmarkClass,
+      ShutdownHookRegistrar shutdownHookRegistrar) {
+    this.trialId = trialId;
+    this.workerBuilder =
+        buildProcess(trialId, experiment, benchmarkSpec, localPort, benchmarkClass);
+    this.openedSocket = openedSocket;
+    this.shutdownHookRegistrar = shutdownHookRegistrar;
+  }
+
+  ListenableFuture<OpenedSocket> socketFuture() {
+    return openedSocket;
+  }
+
+  /**
+   * Returns a {@link Process} representing this worker.  The process will be started if it hasn't
+   * already.
+   */
+  synchronized Process startWorker() throws IOException {
+    if (worker == null) {
+      final Process delegate = workerBuilder.start();
+      final Thread shutdownHook = new Thread("worker-shutdown-hook-" + trialId) {
+        @Override public void run() {
+          delegate.destroy();
+        }
+      };
+      shutdownHookRegistrar.addShutdownHook(shutdownHook);
+      worker = new Process() {
+        @Override public OutputStream getOutputStream() {
+          return delegate.getOutputStream();
+        }
+
+        @Override public InputStream getInputStream() {
+          return delegate.getInputStream();
+        }
+
+        @Override public InputStream getErrorStream() {
+          return delegate.getErrorStream();
+        }
+
+        @Override public int waitFor() throws InterruptedException {
+          int waitFor = delegate.waitFor();
+          shutdownHookRegistrar.removeShutdownHook(shutdownHook);
+          return waitFor;
+        }
+
+        @Override public int exitValue() {
+          int exitValue = delegate.exitValue();
+          // if it hasn't thrown, the process is done
+          shutdownHookRegistrar.removeShutdownHook(shutdownHook);
+          return exitValue;
+        }
+
+        @Override public void destroy() {
+          delegate.destroy();
+          shutdownHookRegistrar.removeShutdownHook(shutdownHook);
+        }
+      };
+    }
+    return worker;
+  }
+  
+  @VisibleForTesting static ProcessBuilder buildProcess(
+      UUID trialId,
+      Experiment experiment,
+      BenchmarkSpec benchmarkSpec,
+      int localPort,
+      BenchmarkClass benchmarkClass) {
+    // TODO(lukes): it would be nice to split this method into a few smaller more targeted methods
+    Instrumentation instrumentation = experiment.instrumentation();
+    Instrument instrument = instrumentation.instrument();
+    WorkerSpec request = new WorkerSpec(
+        trialId,
+        instrumentation.workerClass(),
+        instrumentation.workerOptions(),
+        benchmarkSpec,
+        ImmutableList.copyOf(instrumentation.benchmarkMethod.getParameterTypes()),
+        localPort);
+
+    ProcessBuilder processBuilder = new ProcessBuilder().redirectErrorStream(false);
+
+    List<String> args = processBuilder.command();
+
+    VirtualMachine vm = experiment.vm();
+    VmConfig vmConfig = vm.config;
+    args.addAll(getJvmArgs(vm, benchmarkClass));
+
+    Iterable<String> instrumentJvmOptions = instrument.getExtraCommandLineArgs(vmConfig);
+    logger.fine(String.format("Instrument(%s) Java args: %s", instrument.getClass().getName(),
+        instrumentJvmOptions));
+    Iterables.addAll(args, instrumentJvmOptions);
+
+    // last to ensure that they're always applied
+    args.addAll(vmConfig.workerProcessArgs());
+
+    args.add(WorkerMain.class.getName());
+    args.add(CommandLineSerializer.render(request));
+
+    logger.finest(String.format("Full JVM (%s) args: %s", vm.name, args));
+    return processBuilder;
+  }
+  
+  @VisibleForTesting static List<String> getJvmArgs(
+      VirtualMachine vm,
+      BenchmarkClass benchmarkClass) {
+
+    VmConfig vmConfig = vm.config;
+    String platformName = vmConfig.platformName();
+
+    List<String> args = Lists.newArrayList();
+    String jdkPath = vmConfig.vmExecutable().getAbsolutePath();
+    args.add(jdkPath);
+    logger.fine(String.format("%s(%s) Path: %s", platformName, vm.name, jdkPath));
+
+    ImmutableList<String> jvmOptions = vmConfig.options();
+    args.addAll(jvmOptions);
+    logger.fine(String.format("%s(%s) args: %s", platformName, vm.name, jvmOptions));
+    
+    ImmutableSet<String> benchmarkJvmOptions = benchmarkClass.vmOptions();
+    args.addAll(benchmarkJvmOptions);
+    logger.fine(String.format("Benchmark(%s) %s args: %s", benchmarkClass.name(), platformName,
+        benchmarkJvmOptions));
+
+    String classPath = vmConfig.workerClassPath();
+    Collections.addAll(args, "-cp", classPath);
+    logger.finer(String.format("Class path: %s", classPath));
+    return args;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/util/DisplayUsageException.java b/caliper/src/main/java/com/google/caliper/util/DisplayUsageException.java
new file mode 100644
index 0000000..c412c55
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/DisplayUsageException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.util;
+
+import java.io.PrintWriter;
+
+/**
+ * Exception used to abort command-line processing because the user has asked for help (using either
+ * --help or -h).
+ */
+@SuppressWarnings("serial") // who would serialize a command-line parsing error?
+public final class DisplayUsageException extends InvalidCommandException {
+  public DisplayUsageException() {
+    super("(User asked for --help. This message should not appear anywhere.)");
+  }
+
+  @Override public void display(PrintWriter writer) {
+    displayUsage(writer);
+  }
+
+  @Override public int exitCode() {
+    return 0;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/util/InterleavedReader.java b/caliper/src/main/java/com/google/caliper/util/InterleavedReader.java
deleted file mode 100644
index 40293bf..0000000
--- a/caliper/src/main/java/com/google/caliper/util/InterleavedReader.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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/InvalidCommandException.java b/caliper/src/main/java/com/google/caliper/util/InvalidCommandException.java
new file mode 100644
index 0000000..990ca7b
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/InvalidCommandException.java
@@ -0,0 +1,54 @@
+/*
+ * 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.util;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Exception that signifies that the <i>user</i> has given an invalid argument string.
+ */
+@SuppressWarnings("serial") // who would serialize a command-line parsing error?
+public class InvalidCommandException extends RuntimeException {
+  private ImmutableList<String> usage;
+
+  public InvalidCommandException(String message, Object... args) {
+    super(String.format(message, args));
+  }
+
+  public void setUsage(List<String> usage) {
+    this.usage = ImmutableList.copyOf(usage);
+  }
+
+  public void display(PrintWriter writer) {
+    writer.println(getMessage());
+    if (usage != null) {
+      writer.println();
+      displayUsage(writer);
+    }
+  }
+
+  protected final void displayUsage(PrintWriter writer) {
+    for (String line : usage) {
+      writer.println(line);
+    }
+  }
+
+  public int exitCode() {
+    return 1;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/util/OutputModule.java b/caliper/src/main/java/com/google/caliper/util/OutputModule.java
new file mode 100644
index 0000000..8220436
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/OutputModule.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 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 static com.google.common.base.Preconditions.checkNotNull;
+
+import dagger.Module;
+import dagger.Provides;
+
+import java.io.PrintWriter;
+
+/**
+ * A module that binds {@link PrintWriter} instances for {@link Stdout} and {@link Stderr}.
+ */
+@Module
+public final class OutputModule {
+  public static OutputModule system() {
+    return new OutputModule(new PrintWriter(System.out, true), new PrintWriter(System.err, true));
+  }
+
+  private final PrintWriter stdout;
+  private final PrintWriter stderr;
+
+  public OutputModule(PrintWriter stdout, PrintWriter stderr) {
+    this.stdout = checkNotNull(stdout);
+    this.stderr = checkNotNull(stderr);
+  }
+
+  @Provides @Stdout PrintWriter provideStdoutWriter() {
+    return stdout;
+  }
+
+  @Provides @Stderr PrintWriter provideStderr() {
+    return stderr;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/MeasurementType.java b/caliper/src/main/java/com/google/caliper/util/Parser.java
similarity index 69%
copy from caliper/src/main/java/com/google/caliper/MeasurementType.java
copy to caliper/src/main/java/com/google/caliper/util/Parser.java
index 30de69f..0ab0d9e 100644
--- a/caliper/src/main/java/com/google/caliper/MeasurementType.java
+++ b/caliper/src/main/java/com/google/caliper/util/Parser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * 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.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
+package com.google.caliper.util;
 
-import com.google.common.annotations.GwtCompatible;
+import java.text.ParseException;
 
-@GwtCompatible
-public enum MeasurementType {
-  TIME, INSTANCE, MEMORY, DEBUG
+// TODO(kevinb): release common.text.Parser in Guava then nuke this
+public interface Parser<T> {
+  T parse(CharSequence text) throws ParseException;
 }
diff --git a/caliper/src/main/java/com/google/caliper/util/Parsers.java b/caliper/src/main/java/com/google/caliper/util/Parsers.java
new file mode 100644
index 0000000..3e56f1a
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/Parsers.java
@@ -0,0 +1,111 @@
+/*
+ * 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.util;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.Primitives;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.text.ParseException;
+import java.util.List;
+
+public class Parsers {
+  public static final Parser<String> IDENTITY = new Parser<String>() {
+    @Override public String parse(CharSequence in) {
+      return in.toString();
+    }
+  };
+
+  private static final List<String> CONVERSION_METHOD_NAMES =
+      ImmutableList.of("fromString", "decode", "valueOf");
+
+  /**
+   * Parser that tries, in this order:
+   * <ul>
+   * <li>ResultType.fromString(String)
+   * <li>ResultType.decode(String)
+   * <li>ResultType.valueOf(String)
+   * <li>new ResultType(String)
+   * </ul>
+   */
+  public static <T> Parser<T> conventionalParser(Class<T> resultType)
+      throws NoSuchMethodException {
+    if (resultType == String.class) {
+      @SuppressWarnings("unchecked") // T == String
+      Parser<T> identity = (Parser<T>) IDENTITY;
+      return identity;
+    }
+
+    final Class<T> wrappedResultType = Primitives.wrap(resultType);
+
+    for (String methodName : CONVERSION_METHOD_NAMES) {
+      try {
+        final Method method = wrappedResultType.getDeclaredMethod(methodName, String.class);
+
+        if (Util.isStatic(method) && wrappedResultType.isAssignableFrom(method.getReturnType())) {
+          method.setAccessible(true); // to permit inner enums, etc.
+          return new InvokingParser<T>() {
+            @Override protected T invoke(String input) throws Exception {
+              return wrappedResultType.cast(method.invoke(null, input));
+            }
+          };
+        }
+      } catch (Exception tryAgain) {
+      }
+    }
+
+    final Constructor<T> constr = wrappedResultType.getDeclaredConstructor(String.class);
+    constr.setAccessible(true);
+    return new InvokingParser<T>() {
+      @Override protected T invoke(String input) throws Exception {
+        return wrappedResultType.cast(constr.newInstance(input));
+      }
+    };
+  }
+
+  abstract static class InvokingParser<T> implements Parser<T> {
+    @Override public T parse(CharSequence input) throws ParseException {
+      try {
+        return invoke(input.toString());
+      } catch (InvocationTargetException e) {
+        Throwable cause = e.getCause();
+        String desc = firstNonNull(cause.getMessage(), cause.getClass().getSimpleName());
+        throw newParseException(desc, cause);
+      } catch (Exception e) {
+        throw newParseException("Unknown parsing problem", e);
+      }
+    }
+
+    protected abstract T invoke(String input) throws Exception;
+  }
+
+  public static ParseException newParseException(String message, Throwable cause) {
+    ParseException pe = newParseException(message);
+    pe.initCause(cause);
+    return pe;
+  }
+
+  public static ParseException newParseException(String message) {
+    return new ParseException(message, 0);
+  }
+
+  private static <T> T firstNonNull(T first, T second) {
+    return (first != null) ? first : second;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/util/Reflection.java b/caliper/src/main/java/com/google/caliper/util/Reflection.java
new file mode 100644
index 0000000..4b17d8d
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/Reflection.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 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.collect.ImmutableSet;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+/**
+ * A utility class for common reflection operations in Caliper.
+ */
+public final class Reflection {
+  private Reflection() {}
+
+  public static ImmutableSet<Method> getAnnotatedMethods(Class<?> clazz,
+      Class<? extends Annotation> annotationClass) {
+    Method[] methods = clazz.getDeclaredMethods();
+    ImmutableSet.Builder<Method> builder = ImmutableSet.builder();
+    for (Method method : methods) {
+      if (method.isAnnotationPresent(annotationClass)) {
+        method.setAccessible(true);
+        builder.add(method);
+      }
+    }
+    return builder.build();
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/util/ShortDuration.java b/caliper/src/main/java/com/google/caliper/util/ShortDuration.java
new file mode 100644
index 0000000..34d9a2b
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/ShortDuration.java
@@ -0,0 +1,352 @@
+/*
+ * 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.util;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Ascii;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.primitives.Longs;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+/**
+ * Represents a nonnegative duration from 0 to 100 days, with picosecond precision.
+ * Contrast with Joda-Time's duration class, which has only millisecond precision but can
+ * represent durations of millions of years.
+ */
+public abstract class ShortDuration implements Comparable<ShortDuration> {
+  // Factories
+
+  public static ShortDuration of(long duration, TimeUnit unit) {
+    if (duration == 0) {
+      return ZERO;
+    }
+    checkArgument(duration >= 0, "negative duration: %s", duration);
+    checkArgument(duration <= MAXES.get(unit),
+        "ShortDuration cannot exceed 100 days: %s %s", duration, unit);
+    long nanos = TimeUnit.NANOSECONDS.convert(duration, unit);
+    return new PositiveShortDuration(nanos * 1000);
+  }
+
+  public static ShortDuration of(BigDecimal duration, TimeUnit unit) {
+    // convert to picoseconds first, to minimize rounding
+    BigDecimal picos = duration.multiply(ONE_IN_PICOS.get(unit));
+    return ofPicos(toLong(picos, RoundingMode.HALF_UP));
+  }
+
+  public static ShortDuration valueOf(String s) {
+    if ("0".equals(s)) {
+      return ZERO;
+    }
+    Matcher matcher = PATTERN.matcher(s);
+    checkArgument(matcher.matches(), "Invalid ShortDuration: %s", s);
+
+    BigDecimal value = new BigDecimal(matcher.group(1));
+    String abbrev = matcher.group(2);
+    TimeUnit unit = ABBREV_TO_UNIT.get(abbrev);
+    checkArgument(unit != null, "Unrecognized time unit: %s", abbrev);
+
+    return of(value, unit);
+  }
+
+  public static ShortDuration zero() {
+    return ZERO;
+  }
+
+  // fortunately no abbreviation starts with 'e', so this should work
+  private static final Pattern PATTERN = Pattern.compile("^([0-9.eE+-]+) ?(\\S+)$");
+
+  private static ShortDuration ofPicos(long picos) {
+    if (picos == 0) {
+      return ZERO;
+    }
+    checkArgument(picos > 0);
+    return new PositiveShortDuration(picos);
+  }
+
+  // TODO(kevinb): we sure seem to convert back and forth with BigDecimal a lot.
+  // Why not just *make* this a BigDecimal?
+  final long picos;
+
+  ShortDuration(long picos) {
+    this.picos = picos;
+  }
+
+  public long toPicos() {
+    return picos;
+  }
+
+  public long to(TimeUnit unit) {
+    return to(unit, RoundingMode.HALF_UP);
+  }
+
+  public abstract long to(TimeUnit unit, RoundingMode roundingMode);
+
+  /*
+   * In Guava, this will probably implement an interface called Quantity, and the following methods
+   * will come from there, so they won't have to be defined here.
+   */
+
+  /**
+   * Returns an instance of this type that represents the sum of this value and {@code
+   * addend}.
+   */
+  public abstract ShortDuration plus(ShortDuration addend);
+
+  /**
+   * Returns an instance of this type that represents the difference of this value and
+   * {@code subtrahend}.
+   */
+  public abstract ShortDuration minus(ShortDuration subtrahend);
+
+  /**
+   * Returns an instance of this type that represents the product of this value and the
+   * integral value {@code multiplicand}.
+   */
+  public abstract ShortDuration times(long multiplicand);
+
+  /**
+   * Returns an instance of this type that represents the product of this value and {@code
+   * multiplicand}, rounded according to {@code roundingMode} if necessary.
+   *
+   * <p>If this class represents an amount that is "continuous" rather than discrete, the
+   * implementation of this method may simply ignore the rounding mode.
+   */
+  public abstract ShortDuration times(BigDecimal multiplicand, RoundingMode roundingMode);
+
+  /**
+   * Returns an instance of this type that represents this value divided by the integral
+   * value {@code divisor}, rounded according to {@code roundingMode} if necessary.
+   *
+   * <p>If this class represents an amount that is "continuous" rather than discrete, the
+   * implementation of this method may simply ignore the rounding mode.
+   */
+  public abstract ShortDuration dividedBy(long divisor, RoundingMode roundingMode);
+
+  /**
+   * Returns an instance of this type that represents this value divided by {@code
+   * divisor}, rounded according to {@code roundingMode} if necessary.
+   *
+   * <p>If this class represents an amount that is "continuous" rather than discrete, the
+   * implementation of this method may simply ignore the rounding mode.
+   */
+  public abstract ShortDuration dividedBy(BigDecimal divisor, RoundingMode roundingMode);
+
+  // Zero
+
+  private static ShortDuration ZERO = new ShortDuration(0) {
+    @Override public long to(TimeUnit unit, RoundingMode roundingMode) {
+      return 0;
+    }
+    @Override public ShortDuration plus(ShortDuration addend) {
+      return addend;
+    }
+    @Override public ShortDuration minus(ShortDuration subtrahend) {
+      checkArgument(this == subtrahend);
+      return this;
+    }
+    @Override public ShortDuration times(long multiplicand) {
+      return this;
+    }
+    @Override public ShortDuration times(BigDecimal multiplicand, RoundingMode roundingMode) {
+      return this;
+    }
+    @Override public ShortDuration dividedBy(long divisor, RoundingMode roundingMode) {
+      return dividedBy(new BigDecimal(divisor), roundingMode);
+    }
+    @Override public ShortDuration dividedBy(BigDecimal divisor, RoundingMode roundingMode) {
+      checkArgument(divisor.compareTo(BigDecimal.ZERO) != 0);
+      return this;
+    }
+    @Override public int compareTo(ShortDuration that) {
+      if (this == that) {
+        return 0;
+      }
+      checkNotNull(that);
+      return -1;
+    }
+    @Override public boolean equals(@Nullable Object that) {
+      return this == that;
+    }
+    @Override public int hashCode() {
+      return 0;
+    }
+    @Override public String toString() {
+      return "0s";
+    }
+  };
+
+  // Non-zero
+
+  private static class PositiveShortDuration extends ShortDuration {
+    private PositiveShortDuration(long picos) {
+      super(picos);
+      checkArgument(picos > 0);
+    }
+
+    @Override public long to(TimeUnit unit, RoundingMode roundingMode) {
+      BigDecimal divisor = ONE_IN_PICOS.get(unit);
+      return toLong(new BigDecimal(picos).divide(divisor), roundingMode);
+    }
+
+    @Override public ShortDuration plus(ShortDuration addend) {
+      return new PositiveShortDuration(picos + addend.picos);
+    }
+
+    @Override public ShortDuration minus(ShortDuration subtrahend) {
+      return ofPicos(picos - subtrahend.picos);
+    }
+
+    @Override public ShortDuration times(long multiplicand) {
+      if (multiplicand == 0) {
+        return ZERO;
+      }
+      checkArgument(multiplicand >= 0, "negative multiplicand: %s", multiplicand);
+      checkArgument(multiplicand <= Long.MAX_VALUE / picos,
+          "product of %s and %s would overflow", this, multiplicand);
+      return new PositiveShortDuration(picos * multiplicand);
+    }
+
+    @Override public ShortDuration times(BigDecimal multiplicand, RoundingMode roundingMode) {
+      BigDecimal product = BigDecimal.valueOf(picos).multiply(multiplicand);
+      return ofPicos(toLong(product, roundingMode));
+    }
+
+    @Override public ShortDuration dividedBy(long divisor, RoundingMode roundingMode) {
+      return dividedBy(new BigDecimal(divisor), roundingMode);
+    }
+
+    @Override public ShortDuration dividedBy(BigDecimal divisor, RoundingMode roundingMode) {
+      BigDecimal product = BigDecimal.valueOf(picos).divide(divisor, roundingMode);
+      return ofPicos(product.longValueExact());
+    }
+
+    @Override public int compareTo(ShortDuration that) {
+      return Longs.compare(this.picos, that.picos);
+    }
+
+    @Override public boolean equals(Object object) {
+      if (object instanceof PositiveShortDuration) {
+        PositiveShortDuration that = (PositiveShortDuration) object;
+        return this.picos == that.picos;
+      }
+      return false;
+    }
+
+    @Override public int hashCode() {
+      return Longs.hashCode(picos);
+    }
+
+    @Override public String toString() {
+      TimeUnit bestUnit = TimeUnit.NANOSECONDS;
+      for (TimeUnit unit : TimeUnit.values()) {
+        if (picosIn(unit) > picos) {
+          break;
+        }
+        bestUnit = unit;
+      }
+      BigDecimal divisor = ONE_IN_PICOS.get(bestUnit);
+
+      return new BigDecimal(picos).divide(divisor, ROUNDER) + preferredAbbrev(bestUnit);
+    }
+
+    private static final MathContext ROUNDER = new MathContext(4);
+  }
+
+  // Private parts
+
+  private static String preferredAbbrev(TimeUnit bestUnit) {
+    return ABBREVIATIONS.get(bestUnit).get(0);
+  }
+
+  private static final ImmutableListMultimap<TimeUnit, String> ABBREVIATIONS =
+      createAbbreviations();
+
+  private static ImmutableListMultimap<TimeUnit, String> createAbbreviations() {
+    ImmutableListMultimap.Builder<TimeUnit, String> builder = ImmutableListMultimap.builder();
+    builder.putAll(TimeUnit.NANOSECONDS, "ns", "nanos");
+    builder.putAll(TimeUnit.MICROSECONDS, "\u03bcs" /*μs*/, "us", "micros");
+    builder.putAll(TimeUnit.MILLISECONDS, "ms", "millis");
+    builder.putAll(TimeUnit.SECONDS, "s", "sec");
+
+    // Do the rest in a JDK5-safe way
+    TimeUnit[] allUnits = TimeUnit.values();
+    if (allUnits.length >= 7) {
+      builder.putAll(allUnits[4], "m", "min");
+      builder.putAll(allUnits[5], "h", "hr");
+      builder.putAll(allUnits[6], "d");
+    }
+
+    for (TimeUnit unit : TimeUnit.values()) {
+      builder.put(unit, Ascii.toLowerCase(unit.name()));
+    }
+    return builder.build();
+  }
+
+  private static final Map<String, TimeUnit> ABBREV_TO_UNIT = createAbbrevToUnitMap();
+
+  private static Map<String, TimeUnit> createAbbrevToUnitMap() {
+    ImmutableMap.Builder<String, TimeUnit> builder = ImmutableMap.builder();
+    for (Map.Entry<TimeUnit, String> entry : ABBREVIATIONS.entries()) {
+      builder.put(entry.getValue(), entry.getKey());
+    }
+    return builder.build();
+  }
+
+  private static final Map<TimeUnit, BigDecimal> ONE_IN_PICOS = createUnitToPicosMap();
+
+  private static Map<TimeUnit, BigDecimal> createUnitToPicosMap() {
+    Map<TimeUnit, BigDecimal> map = Maps.newEnumMap(TimeUnit.class);
+    for (TimeUnit unit : TimeUnit.values()) {
+      map.put(unit, new BigDecimal(picosIn(unit)));
+    }
+    return Collections.unmodifiableMap(map);
+  }
+
+  private static final Map<TimeUnit, Long> MAXES = createMaxesMap();
+
+  private static Map<TimeUnit, Long> createMaxesMap() {
+    Map<TimeUnit, Long> map = Maps.newEnumMap(TimeUnit.class);
+    for (TimeUnit unit : TimeUnit.values()) {
+      // Max is 100 days
+      map.put(unit, unit.convert(100L * 24 * 60 * 60, TimeUnit.SECONDS));
+    }
+    return Collections.unmodifiableMap(map);
+  }
+
+  private static long toLong(BigDecimal bd, RoundingMode roundingMode) {
+    // setScale does not really mutate the BigDecimal
+    return bd.setScale(0, roundingMode).longValueExact();
+  }
+
+  private static long picosIn(TimeUnit unit) {
+    return unit.toNanos(1000);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/util/Stderr.java b/caliper/src/main/java/com/google/caliper/util/Stderr.java
new file mode 100644
index 0000000..7aa9e1f
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/Stderr.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 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 static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/** A binding annotation for standard err. */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+public @interface Stderr {}
diff --git a/caliper/src/main/java/com/google/caliper/util/Stdout.java b/caliper/src/main/java/com/google/caliper/util/Stdout.java
new file mode 100644
index 0000000..26acf6d
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/Stdout.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 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 static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/** A binding annotation for standard out. */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+public @interface Stdout {}
diff --git a/caliper/src/main/java/com/google/caliper/util/Util.java b/caliper/src/main/java/com/google/caliper/util/Util.java
new file mode 100644
index 0000000..a3dc3f7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/Util.java
@@ -0,0 +1,154 @@
+/*
+ * 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.util;
+
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteSource;
+import com.google.common.io.Closer;
+import com.google.common.io.Resources;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Member;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public final class Util {
+  private Util() {}
+
+  // Users have no idea that nested classes are identified with '$', not '.', so if class lookup
+  // fails try replacing the last . with $.
+  public static Class<?> lenientClassForName(String className) throws ClassNotFoundException {
+    try {
+      return loadClass(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 loadClass(newName);
+    }
+  }
+
+  /**
+   * Search for a class by name.
+   *
+   * @param className the name of the class.
+   * @return the class.
+   * @throws ClassNotFoundException if the class could not be found.
+   */
+  public static Class<?> loadClass(String className) throws ClassNotFoundException {
+    // Use the thread context class loader. This is necessary because in some configurations, e.g.
+    // when run from a single JAR containing caliper and all its dependencies the caliper JAR
+    // ends up on the boot class path of the Worker and so needs to the use thread context class
+    // loader to load classes provided by the user.
+    return Class.forName(className, true, Thread.currentThread().getContextClassLoader());
+  }
+
+  public static ImmutableMap<String, String> loadProperties(ByteSource is) throws IOException {
+    Properties props = new Properties();
+    Closer closer = Closer.create();
+    InputStream in = closer.register(is.openStream());
+    try {
+      props.load(in);
+    } finally {
+      closer.close();
+    }
+    return Maps.fromProperties(props);
+  }
+
+  public static ByteSource resourceSupplier(final Class<?> c, final String name) {
+    return Resources.asByteSource(c.getResource(name));
+  }
+
+  private static <T> ImmutableMap<String, T> prefixedSubmap(
+      Map<String, T> props, String prefix) {
+    ImmutableMap.Builder<String, T> submapBuilder = ImmutableMap.builder();
+    for (Map.Entry<String, T> entry : props.entrySet()) {
+      String name = entry.getKey();
+      if (name.startsWith(prefix)) {
+        submapBuilder.put(name.substring(prefix.length()), entry.getValue());
+      }
+    }
+    return submapBuilder.build();
+  }
+
+  /**
+   * Returns a map containing only those entries whose key starts with {@code <groupName>.}.
+   *
+   * <p>The keys in the returned map have had their {@code <groupName>.} prefix removed.
+   *
+   * <p>e.g. If given a map that contained {@code group.key1 -> value1, key2 -> value2} and a
+   * {@code groupName} of {@code group} it would produce a map containing {@code key1 -> value1}.
+   */
+  public static ImmutableMap<String, String> subgroupMap(
+          Map<String, String> map, String groupName) {
+    return prefixedSubmap(map, groupName + ".");
+  }
+
+  public static boolean isPublic(Member member) {
+    return Modifier.isPublic(member.getModifiers());
+  }
+
+  public static boolean isStatic(Member member) {
+    return Modifier.isStatic(member.getModifiers());
+  }
+
+  private static final long FORCE_GC_TIMEOUT_SECS = 2;
+
+  public static void forceGc() {
+    System.gc();
+    System.runFinalization();
+    final CountDownLatch latch = new CountDownLatch(1);
+    new Object() {
+      @Override protected void finalize() {
+        latch.countDown();
+      }
+    };
+    System.gc();
+    System.runFinalization();
+    try {
+      latch.await(FORCE_GC_TIMEOUT_SECS, TimeUnit.SECONDS);
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+    }
+  }
+
+  public static <T> ImmutableBiMap<T, String> assignNames(Set<T> items) {
+    ImmutableList<T> itemList = ImmutableList.copyOf(items);
+    ImmutableBiMap.Builder<T, String> itemNamesBuilder = ImmutableBiMap.builder();
+    for (int i = 0; i < itemList.size(); i++) {
+      itemNamesBuilder.put(itemList.get(i), generateUniqueName(i));
+    }
+    return itemNamesBuilder.build();
+  }
+
+  private static String generateUniqueName(int index) {
+    if (index < 26) {
+      return String.valueOf((char) ('A' + index));
+    } else {
+      return generateUniqueName(index / 26 - 1) + generateUniqueName(index % 26);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/AggregateAllocationsRecorder.java b/caliper/src/main/java/com/google/caliper/worker/AggregateAllocationsRecorder.java
new file mode 100644
index 0000000..96fe5e1
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/AggregateAllocationsRecorder.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 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.worker;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.monitoring.runtime.instrumentation.Sampler;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.inject.Inject;
+
+/**
+ * An {@link AllocationRecorder} that records the number and cumulative size of allocation.
+ */
+final class AggregateAllocationsRecorder extends AllocationRecorder {
+  private final AtomicInteger allocationCount = new AtomicInteger();
+  private final AtomicLong allocationSize = new AtomicLong();
+  private volatile boolean recording = false;
+  
+  private final Sampler sampler = new Sampler() {
+    @Override public void sampleAllocation(int arrayCount, String desc, Object newObj, 
+        long size) {
+      if (recording) {
+        allocationCount.getAndIncrement();
+        allocationSize.getAndAdd(size);
+      }
+    }
+  };
+  
+  @Inject AggregateAllocationsRecorder() {
+    com.google.monitoring.runtime.instrumentation.AllocationRecorder.addSampler(sampler);
+  }
+  
+  @Override protected void doStartRecording() {
+    checkState(!recording, "startRecording called, but we were already recording.");
+    allocationCount.set(0);
+    allocationSize.set(0);
+    recording = true;
+  }
+  
+  @Override public AllocationStats stopRecording(int reps) {
+    checkState(recording, "stopRecording called, but we were not recording.");
+    recording = false;
+    return new AllocationStats(allocationCount.get(), allocationSize.get(), reps);
+  }
+}
\ No newline at end of file
diff --git a/caliper/src/main/java/com/google/caliper/worker/AllAllocationsRecorder.java b/caliper/src/main/java/com/google/caliper/worker/AllAllocationsRecorder.java
new file mode 100644
index 0000000..ae9baef
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/AllAllocationsRecorder.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 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.worker;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Arrays.asList;
+
+import com.google.caliper.runner.Running;
+import com.google.common.collect.ConcurrentHashMultiset;
+import com.google.monitoring.runtime.instrumentation.Sampler;
+
+import javax.inject.Inject;
+
+/**
+ * An {@link AllocationRecorder} that records every allocation and its location.
+ * 
+ * <p>This recorder is enabled via the {@code trackAllocations} worker option.
+ */
+final class AllAllocationsRecorder extends AllocationRecorder {
+  private final Class<?> benchmarkClass;
+  private final String benchmarkMethodName;
+  private volatile boolean recording = false;
+  private final ConcurrentHashMultiset<Allocation> allocations = ConcurrentHashMultiset.create();
+  
+  private final Sampler sampler = new Sampler() {
+    @Override public void sampleAllocation(int arrayCount, String desc, Object newObj, 
+        long size) {
+      if (recording) {
+        if (arrayCount != -1) {
+          desc = desc + "[" + arrayCount + "]";
+        }
+        // The first item is this line, the second is in AllocationRecorder and the
+        // one before that is the allocating line, so we start at index 2.
+        // We want to grab all lines until we get into the benchmark method.
+        StackTraceElement[] stackTrace = new Exception().getStackTrace();
+        int startIndex = 2;
+        int endIndex = 2;
+        for (int i = startIndex; i < stackTrace.length; i++) {
+          StackTraceElement element = stackTrace[i];
+          if (element.getClassName().startsWith(
+              AllAllocationsRecorder.class.getPackage().getName())) {
+            // Don't track locations up into the worker code, or originating within the worker 
+            // code.
+            break;
+          }
+          endIndex = i;
+          if (element.getClassName().equals(benchmarkClass.getName()) 
+              && element.getMethodName().equals(benchmarkMethodName)) {
+            // stop logging at the method under test
+            break;
+          }
+        }
+        allocations.add(
+            new Allocation(desc, size, asList(stackTrace).subList(startIndex, endIndex + 1)));
+      }
+    }
+  };
+  
+  @Inject AllAllocationsRecorder(@Running.BenchmarkClass Class<?> benchmarkClass, 
+      @Running.BenchmarkMethod String benchmarkMethodName) {
+    this.benchmarkClass = benchmarkClass;
+    this.benchmarkMethodName = benchmarkMethodName;
+    com.google.monitoring.runtime.instrumentation.AllocationRecorder.addSampler(sampler);
+  }
+  
+  @Override protected void doStartRecording() {
+    checkState(!recording, "startRecording called, but we were already recording.");
+    allocations.clear();
+    recording = true;
+  }
+  
+  @Override public AllocationStats stopRecording(int reps) {
+    checkState(recording, "stopRecording called, but we were not recording.");
+    recording = false;
+    return new AllocationStats(allocations, reps);
+  }
+}
\ No newline at end of file
diff --git a/caliper/src/main/java/com/google/caliper/worker/Allocation.java b/caliper/src/main/java/com/google/caliper/worker/Allocation.java
new file mode 100644
index 0000000..d855dc0
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/Allocation.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 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.worker;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Interner;
+import com.google.common.collect.Interners;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Data about a particular allocation performed by a benchmark.  This tracks a human readable 
+ * description of the allocation (e.g. 'int[23]', 'java.lang.Integer', or 'java.util.ArrayList'), 
+ * the total size of the allocation in bytes and the location, which is a stringified stack trace of
+ * the allocation.
+ */
+final class Allocation {
+  // While particular lists of STEs can have a lot of variety within a benchmark there don't tend
+  // to be many individually unique STEs.  This can save a lot of memory.
+  // Within a benchmark the code paths should be fairly uniform so it should be safe to just store
+  // these forever.
+  private static final Interner<StackTraceElement> steInterner = Interners.newWeakInterner();
+  private static final Interner<String> descriptionInterner = Interners.newWeakInterner();
+  
+  /** Returns the sum of the {@link #size sizes} of the allocations. */
+  static long getTotalSize(Collection<Allocation> allocations) {
+    long totalSize = 0;
+    for (Allocation allocation : allocations) {
+      totalSize += allocation.size;
+    }
+    return totalSize;
+  }
+  
+  private final String description;
+  private final long size;
+  private final ImmutableList<StackTraceElement> location;
+  
+  Allocation(String description, long size, List<StackTraceElement> location) {
+    this.description = descriptionInterner.intern(description);
+    this.size = size;
+    ImmutableList.Builder<StackTraceElement> locationBuilder = ImmutableList.builder();
+    for (StackTraceElement ste : location) {
+      locationBuilder.add(steInterner.intern(ste));
+    }
+    this.location = locationBuilder.build();
+  }
+  
+  @Override public boolean equals(Object obj) {
+    if (obj instanceof Allocation) {
+      Allocation other = (Allocation) obj;
+      return other.description.equals(description)
+          && other.size == size
+          && other.location.equals(location);
+      
+    }
+    return false;
+  }
+  
+  public String getDescription() {
+    return description;
+  }
+  
+  public long getSize() {
+    return size;
+  }
+  
+  @Override public int hashCode() {
+    return Objects.hashCode(description, size, location);
+  }
+  
+  @Override public String toString() {
+    StringBuilder builder = new StringBuilder();
+    builder.append(description).append(" (").append(size).append(" bytes)\n\tat ");
+    Joiner.on("\n\tat ").appendTo(builder, location);
+    return builder.toString();
+  }
+}
\ No newline at end of file
diff --git a/caliper/src/main/java/com/google/caliper/worker/AllocationRecorder.java b/caliper/src/main/java/com/google/caliper/worker/AllocationRecorder.java
new file mode 100644
index 0000000..3996e09
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/AllocationRecorder.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 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.worker;
+
+/**
+ * An object that records all allocations that occur between {@link #startRecording()} 
+ * and {@link #stopRecording(int)}.
+ * 
+ * <p>This object can accurately track allocations made from multiple threads but is only 
+ * expected to have {@link #startRecording()} and {@link #stopRecording(int)} called by a single 
+ * thread.
+ */
+abstract class AllocationRecorder {
+  private boolean firstTime = true;
+  
+  /** 
+   * Clears the prior state and starts a new recording.
+   * 
+   * @throws IllegalStateException if the recording infrastructure is misconfigured.
+   */
+  final void startRecording() {
+    if (firstTime) {
+      Object obj;
+      doStartRecording();
+      obj = new Object();
+      AllocationStats stats = stopRecording(1);
+      if (stats.getAllocationCount() != 1 || stats.getAllocationSize() < 1) {
+        throw new IllegalStateException(
+            String.format("The allocation recording infrastructure appears to be broken. "
+                + "Expected to find exactly one allocation of a java/lang/Object instead found %s",
+                stats));
+      }
+      firstTime = false;
+    }
+    doStartRecording();
+  }
+  
+  /** Clears the prior state and starts a new recording. */
+  protected abstract void doStartRecording();
+  
+  /**
+   * Stops recording allocations and saves all the allocation data recorded since the previous call
+   * to {@link #startRecording()} to an {@link AllocationStats} object.
+   * 
+   * @param reps The number of reps that the previous set of allocation represents.
+   */
+  abstract AllocationStats stopRecording(int reps);
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/AllocationStats.java b/caliper/src/main/java/com/google/caliper/worker/AllocationStats.java
new file mode 100644
index 0000000..af3634c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/AllocationStats.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2013 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.worker;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.caliper.model.Measurement;
+import com.google.caliper.model.Value;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.Multiset.Entry;
+import com.google.common.collect.Multisets;
+
+import java.text.DecimalFormat;
+import java.util.Collection;
+
+/**
+ * A set of statistics about the allocations performed by a benchmark method.
+ */
+final class AllocationStats {
+  private final int allocationCount;
+  private final long allocationSize;
+  private final int reps;
+  private final ImmutableMultiset<Allocation> allocations;
+  
+  /**
+   * Constructs a new {@link AllocationStats} with the given number of allocations 
+   * ({@code allocationCount}), cumulative size of the allocations ({@code allocationSize}) and the
+   * number of {@code reps} passed to the benchmark method.
+   */
+  AllocationStats(int allocationCount, long allocationSize, int reps) {
+    this(allocationCount, allocationSize, reps, ImmutableMultiset.<Allocation>of());
+  }
+  
+  /**
+   * Constructs a new {@link AllocationStats} with the given allocations and the number of 
+   * {@code reps} passed to the benchmark method.
+   */
+  AllocationStats(Collection<Allocation> allocations, int reps) {
+    this(allocations.size(), Allocation.getTotalSize(allocations), reps, 
+        ImmutableMultiset.copyOf(allocations));
+  }
+
+  private AllocationStats(int allocationCount, long allocationSize, int reps, 
+      Multiset<Allocation> allocations) {
+    checkArgument(allocationCount >= 0, "allocationCount (%s) was negative", allocationCount);
+    this.allocationCount = allocationCount;
+    checkArgument(allocationSize >= 0, "allocationSize (%s) was negative", allocationSize);
+    this.allocationSize = allocationSize;
+    checkArgument(reps >= 0, "reps (%s) was negative", reps);
+    this.reps = reps;
+    this.allocations = Multisets.copyHighestCountFirst(allocations);
+  }
+  
+  int getAllocationCount() {
+    return allocationCount;
+  }
+  
+  long getAllocationSize() {
+    return allocationSize;
+  }
+  
+  /**
+   * Computes and returns the difference between this measurement and the given 
+   * {@code baseline} measurement. The {@code baseline} measurement must have a lower weight 
+   * (fewer reps) than this measurement.
+   */
+  AllocationStats minus(AllocationStats baseline) {
+    for (Entry<Allocation> entry : baseline.allocations.entrySet()) {
+      int superCount = allocations.count(entry.getElement());
+      if (superCount < entry.getCount()) {
+        throw new IllegalStateException(
+            String.format("Your benchmark appears to have non-deterministic allocation behavior. "
+                + "Observed %d instance(s) of %s in the baseline but only %d in the actual "
+                + "measurement", 
+                entry.getCount(),
+                entry.getElement(), 
+                superCount));
+      }
+    }
+    try {
+      return new AllocationStats(allocationCount - baseline.allocationCount,
+            allocationSize - baseline.allocationSize,
+            reps - baseline.reps,
+            Multisets.difference(allocations, baseline.allocations));
+    } catch (IllegalArgumentException e) {
+      throw new IllegalStateException(String.format(
+          "Your benchmark appears to have non-deterministic allocation behavior. The difference "
+          + "between the baseline %s and the measurement %s is invalid. Consider enabling "
+          + "instrument.allocation.options.trackAllocations to get a more specific error message.", 
+          baseline, this), e);
+    }
+  }
+
+  /**
+   * Computes and returns the difference between this measurement and the given
+   * {@code baseline} measurement. Unlike {@link #minus(AllocationStats)} this does not have to
+   * be a super set of the baseline.
+   */
+  public Delta delta(AllocationStats baseline) {
+    return new Delta(
+        allocationCount - baseline.allocationCount,
+        allocationSize - baseline.allocationSize,
+        reps - baseline.reps,
+        Multisets.difference(allocations, baseline.allocations),
+        Multisets.difference(baseline.allocations, allocations));
+  }
+
+  /**
+   * Returns a list of {@link Measurement measurements} based on this collection of stats.
+   */
+  ImmutableList<Measurement> toMeasurements() {
+    for (Entry<Allocation> entry : allocations.entrySet()) {
+      double allocsPerRep = ((double) entry.getCount()) / reps;
+      System.out.printf("Allocated %f allocs per rep of %s%n", allocsPerRep, entry.getElement());
+    }
+    return ImmutableList.of(
+        new Measurement.Builder()
+            .value(Value.create(allocationCount, ""))
+            .description("objects")
+            .weight(reps)
+            .build(),
+        new Measurement.Builder()
+            .value(Value.create(allocationSize, "B"))
+            .weight(reps)
+            .description("bytes")
+            .build());
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof AllocationStats) {
+      AllocationStats that = (AllocationStats) obj;
+      return allocationCount == that.allocationCount
+          && allocationSize == that.allocationSize
+          && reps == that.reps
+          && Objects.equal(allocations, that.allocations);
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(allocationCount, allocationSize, reps, allocations);
+  }
+
+  @Override public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("allocationCount", allocationCount)
+        .add("allocationSize", allocationSize)
+        .add("reps", reps)
+        .add("allocations", allocations)
+        .toString();
+  }
+
+  /**
+   * The delta between two different sets of statistics.
+   */
+  static final class Delta {
+    private final int count;
+    private final long size;
+    private final int reps;
+    private final Multiset<Allocation> additions;
+    private final Multiset<Allocation> removals;
+
+    Delta(
+        int count,
+        long size,
+        int reps,
+        Multiset<Allocation> additions,
+        Multiset<Allocation> removals) {
+      this.count = count;
+      this.size = size;
+      this.reps = reps;
+      this.additions = additions;
+      this.removals = removals;
+    }
+
+    /**
+     * Returns the long formatted with a leading +/- sign
+     */
+    private static String formatWithLeadingSign(long n) {
+      return n > 0 ? "+" + n : "" + n;
+    }
+
+    @Override public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("count", formatWithLeadingSign(count))
+          .add("size", formatWithLeadingSign(size))
+          .add("reps", formatWithLeadingSign(reps))
+          .add("additions", additions)
+          .add("removals", removals)
+          .toString();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/ArbitraryMeasurementWorker.java b/caliper/src/main/java/com/google/caliper/worker/ArbitraryMeasurementWorker.java
new file mode 100644
index 0000000..75466f8
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/ArbitraryMeasurementWorker.java
@@ -0,0 +1,73 @@
+/*
+ * 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.worker;
+
+import com.google.caliper.model.ArbitraryMeasurement;
+import com.google.caliper.model.Measurement;
+import com.google.caliper.model.Value;
+import com.google.caliper.runner.Running.Benchmark;
+import com.google.caliper.runner.Running.BenchmarkMethod;
+import com.google.caliper.util.Util;
+import com.google.common.collect.ImmutableSet;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Worker for arbitrary measurements.
+ */
+public final class ArbitraryMeasurementWorker extends Worker {
+  private final Options options;
+  private final String unit;
+  private final String description;
+
+  @Inject ArbitraryMeasurementWorker(
+      @Benchmark Object benchmark, 
+      @BenchmarkMethod Method method,
+      @WorkerOptions Map<String, String> workerOptions) {
+    super(benchmark, method);
+    this.options = new Options(workerOptions);
+    ArbitraryMeasurement annotation = method.getAnnotation(ArbitraryMeasurement.class);
+    this.unit = annotation.units();
+    this.description = annotation.description();
+  }
+
+  @Override public void preMeasure(boolean inWarmup) throws Exception {
+    if (options.gcBeforeEach && !inWarmup) {
+      Util.forceGc();
+    }
+  }
+  
+  @Override public Iterable<Measurement> measure() throws Exception {
+    double measured = (Double) benchmarkMethod.invoke(benchmark);
+    return ImmutableSet.of(new Measurement.Builder()
+        .value(Value.create(measured, unit))
+        .weight(1)
+        .description(description)
+        .build());
+  }
+
+  private static class Options {
+    final boolean gcBeforeEach;
+
+    Options(Map<String, String> options) {
+      this.gcBeforeEach = Boolean.parseBoolean(options.get("gcBeforeEach"));
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/MacrobenchmarkAllocationWorker.java b/caliper/src/main/java/com/google/caliper/worker/MacrobenchmarkAllocationWorker.java
new file mode 100644
index 0000000..45af4f7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/MacrobenchmarkAllocationWorker.java
@@ -0,0 +1,56 @@
+/*
+ * 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.worker;
+
+import com.google.caliper.model.Measurement;
+import com.google.caliper.runner.Running.Benchmark;
+import com.google.caliper.runner.Running.BenchmarkMethod;
+import com.google.common.collect.ImmutableList;
+
+import java.lang.reflect.Method;
+
+import javax.inject.Inject;
+
+/**
+ * The {@link Worker} for the {@code AllocationInstrument}.  This class invokes the benchmark method
+ * a few times, with varying numbers of reps, and computes the number of object allocations and the
+ * total size of those allocations.
+ */
+public final class MacrobenchmarkAllocationWorker extends Worker {
+  private final AllocationRecorder recorder;
+
+  @Inject MacrobenchmarkAllocationWorker(@Benchmark Object benchmark, 
+      @BenchmarkMethod Method method, AllocationRecorder recorder) {
+    super(benchmark, method);
+    this.recorder = recorder;
+  }
+
+  @Override public void bootstrap() throws Exception {
+    // do one initial measurement and throw away its results
+    measureAllocations(benchmark, benchmarkMethod);
+  }
+  
+  @Override public ImmutableList<Measurement> measure() throws Exception {
+    return measureAllocations(benchmark, benchmarkMethod).toMeasurements();
+  }
+
+  private AllocationStats measureAllocations(Object benchmark, Method method) throws Exception {
+    recorder.startRecording();
+    method.invoke(benchmark);
+    return recorder.stopRecording(1);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/MacrobenchmarkWorker.java b/caliper/src/main/java/com/google/caliper/worker/MacrobenchmarkWorker.java
new file mode 100644
index 0000000..5960084
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/MacrobenchmarkWorker.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013 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.worker;
+
+import static com.google.caliper.util.Reflection.getAnnotatedMethods;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import com.google.caliper.api.AfterRep;
+import com.google.caliper.api.BeforeRep;
+import com.google.caliper.model.Measurement;
+import com.google.caliper.model.Value;
+import com.google.caliper.runner.Running.Benchmark;
+import com.google.caliper.runner.Running.BenchmarkMethod;
+import com.google.caliper.util.Util;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Ticker;
+import com.google.common.collect.ImmutableSet;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * The {@link Worker} implementation for macrobenchmarks.
+ */
+public class MacrobenchmarkWorker extends Worker {
+  private final Stopwatch stopwatch;
+  private final ImmutableSet<Method> beforeRepMethods;
+  private final ImmutableSet<Method> afterRepMethods;
+  private final boolean gcBeforeEach;
+
+  @Inject MacrobenchmarkWorker(@Benchmark Object benchmark, @BenchmarkMethod Method method,
+      Ticker ticker, @WorkerOptions Map<String, String> workerOptions) {
+    super(benchmark, method);
+    this.stopwatch = Stopwatch.createUnstarted(ticker);
+    this.beforeRepMethods =
+        getAnnotatedMethods(benchmark.getClass(), BeforeRep.class);
+    this.afterRepMethods =
+        getAnnotatedMethods(benchmark.getClass(), AfterRep.class);
+    this.gcBeforeEach = Boolean.parseBoolean(workerOptions.get("gcBeforeEach"));
+  }
+
+  @Override public void preMeasure(boolean inWarmup) throws Exception {
+    for (Method beforeRepMethod : beforeRepMethods) {
+      beforeRepMethod.invoke(benchmark);
+    }
+    if (gcBeforeEach && !inWarmup) {
+      Util.forceGc();
+    }
+  }
+
+  @Override public Iterable<Measurement> measure() throws Exception {
+    stopwatch.start();
+    benchmarkMethod.invoke(benchmark);
+    long nanos = stopwatch.stop().elapsed(NANOSECONDS);
+    stopwatch.reset();
+    return ImmutableSet.of(new Measurement.Builder()
+        .description("runtime")
+        .weight(1)
+        .value(Value.create(nanos, "ns"))
+        .build());
+  }
+
+  @Override public void postMeasure() throws Exception {
+    for (Method afterRepMethod : afterRepMethods) {
+      afterRepMethod.invoke(benchmark);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/MicrobenchmarkAllocationWorker.java b/caliper/src/main/java/com/google/caliper/worker/MicrobenchmarkAllocationWorker.java
new file mode 100644
index 0000000..a4797b0
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/MicrobenchmarkAllocationWorker.java
@@ -0,0 +1,144 @@
+/*
+ * 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.worker;
+
+import com.google.caliper.model.Measurement;
+import com.google.caliper.runner.Running.Benchmark;
+import com.google.caliper.runner.Running.BenchmarkMethod;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import javax.inject.Inject;
+
+/**
+ * The {@link Worker} for the {@code AllocationInstrument}.  This class invokes the benchmark method
+ * a few times, with varying numbers of reps, and computes the number of object allocations and the
+ * total size of those allocations.
+ */
+public final class MicrobenchmarkAllocationWorker extends Worker {
+  // TODO(gak): make this or something like this an option
+  private static final int WARMUP_REPS = 10;
+  private static final int MAX_REPS = 100;
+
+  /**
+   * The number of consecutive measurement runs that must have matching allocations during the warm
+   * up in order for the method to be determined to be deterministic.
+   */
+  private static final int DETERMINISTIC_BENCHMARK_THRESHOLD = 2;
+
+  /**
+   * The maximum number of warm up measurements to take before determining that the test is
+   * non-deterministic.
+   */
+  private static final int DETERMINISTIC_MEASUREMENT_COUNT = DETERMINISTIC_BENCHMARK_THRESHOLD + 3;
+
+  private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+  private final Random random;
+  private final AllocationRecorder recorder;
+
+  @Inject MicrobenchmarkAllocationWorker(@Benchmark Object benchmark,
+      @BenchmarkMethod Method method, AllocationRecorder recorder, Random random) {
+    super(benchmark, method);
+    this.random = random;
+    this.recorder = recorder;
+  }
+
+  @Override public void bootstrap() throws Exception {
+    // do some initial measurements and throw away the results. this warms up the bootstrap method
+    // itself and also the method invocation path for calling that method.
+
+    // warm up the loop in the benchmark method.
+    measureAllocations(benchmark, benchmarkMethod, WARMUP_REPS);
+
+    // verify that the benchmark is deterministic in terms of the measured allocations.
+    verifyBenchmarkIsDeterministic();
+  }
+
+  /**
+   * Verify the determinism of the benchmark method.
+   *
+   * <p>The method invocation path, i.e. the code that the JVM executes to invoke the method, can
+   * vary depending on how many times it is run with a corresponding effect on the allocations
+   * measured. The invocations performed by this method should be sufficient to cause the JVM to
+   * settle on a single path for invoking the benchmark method and so cause identical allocations
+   * for each subsequent invocation. If tests start to fail with lots of non-deterministic
+   * allocation errors then it's possible that additional invocations are required in which case
+   * the value of {@link #DETERMINISTIC_BENCHMARK_THRESHOLD} should be increased.
+   */
+  private void verifyBenchmarkIsDeterministic() throws Exception {
+    // keep track of all the statistics generated while warming up the method invocation path.
+    List<AllocationStats> history = new ArrayList<AllocationStats>();
+
+    // warm up the method invocation path by calling the benchmark multiple times with 0 reps.
+    AllocationStats baseline = null;
+    int matchingSequenceLength = 1;
+    for (int i = 0; i < DETERMINISTIC_MEASUREMENT_COUNT; ++i) {
+      AllocationStats stats = measureAllocations(benchmark, benchmarkMethod, 0);
+      history.add(stats);
+      if (stats.equals(baseline)) {
+        // if consecutive measurements with the same allocation characteristics reaches the
+        // threshold then treat the benchmark as being deterministic.
+        if (++matchingSequenceLength == DETERMINISTIC_BENCHMARK_THRESHOLD) {
+          return;
+        }
+      } else {
+        matchingSequenceLength = 1;
+        baseline = stats;
+      }
+    }
+
+    // the baseline allocations did not settle down and so are probably non-deterministic.
+    StringBuilder builder = new StringBuilder(100);
+    AllocationStats previous = null;
+    for (AllocationStats allocationStats : history) {
+      if (previous == null) {
+        builder.append(LINE_SEPARATOR).append("  ").append(allocationStats);
+      } else {
+        AllocationStats.Delta delta = allocationStats.delta(previous);
+        builder.append(LINE_SEPARATOR).append("  ").append(delta);
+      }
+      previous = allocationStats;
+    }
+    throw new IllegalStateException(String.format(
+        "Your benchmark appears to have non-deterministic allocation behavior. "
+        + "During the warm up process there was no consecutive sequence of %d runs with"
+        + " identical allocations. The allocation history is:%s",
+        DETERMINISTIC_BENCHMARK_THRESHOLD, builder));
+  }
+
+  @Override public Iterable<Measurement> measure() throws Exception {
+    AllocationStats baseline = measureAllocations(benchmark, benchmarkMethod, 0);
+    // [1, MAX_REPS]
+    int measurementReps = random.nextInt(MAX_REPS) + 1;
+    AllocationStats measurement = measureAllocations(benchmark, benchmarkMethod, measurementReps);
+    return measurement.minus(baseline).toMeasurements();
+  }
+
+  private AllocationStats measureAllocations(
+      Object benchmark, Method method, int reps) throws Exception {
+    // do the Integer boxing and the creation of the Object[] outside of the record block, so that
+    // our internal allocations aren't counted in the benchmark's allocations.
+    Object[] args = {reps};
+    recorder.startRecording();
+    method.invoke(benchmark, args);
+    return recorder.stopRecording(reps);
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/RuntimeWorker.java b/caliper/src/main/java/com/google/caliper/worker/RuntimeWorker.java
new file mode 100644
index 0000000..7d96229
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/RuntimeWorker.java
@@ -0,0 +1,152 @@
+/*
+ * 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.worker;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import com.google.caliper.model.Measurement;
+import com.google.caliper.model.Value;
+import com.google.caliper.runner.InvalidBenchmarkException;
+import com.google.caliper.runner.Running.Benchmark;
+import com.google.caliper.runner.Running.BenchmarkMethod;
+import com.google.caliper.util.ShortDuration;
+import com.google.caliper.util.Util;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Ticker;
+import com.google.common.collect.ImmutableSet;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Random;
+
+import javax.inject.Inject;
+
+/**
+ * A {@link Worker} base class for micro and pico benchmarks.
+ */
+public abstract class RuntimeWorker extends Worker {
+  @VisibleForTesting static final int INITIAL_REPS = 100;
+
+  protected final Random random;
+  protected final Ticker ticker;
+  protected final Options options;
+  private long totalReps;
+  private long totalNanos;
+  private long nextReps;
+
+  RuntimeWorker(Object benchmark, 
+      Method method, Random random, Ticker ticker,
+      Map<String, String> workerOptions) {
+    super(benchmark, method);
+    this.random = random;
+    // TODO(gak): investigate whether or not we can use Stopwatch
+    this.ticker = ticker;
+    this.options = new Options(workerOptions);
+  }
+  
+  @Override public void bootstrap() throws Exception {
+    totalReps = INITIAL_REPS;
+    totalNanos = invokeTimeMethod(INITIAL_REPS);
+  }
+
+  @Override public void preMeasure(boolean inWarmup) throws Exception {
+    nextReps = calculateTargetReps(totalReps, totalNanos, options.timingIntervalNanos,
+        random.nextGaussian());
+    if (options.gcBeforeEach && !inWarmup) {
+      Util.forceGc();
+    }
+  }
+  
+  @Override public Iterable<Measurement> measure() throws Exception {
+    long nanos = invokeTimeMethod(nextReps);
+    Measurement measurement = new Measurement.Builder()
+        .description("runtime")
+        .value(Value.create(nanos, "ns"))
+        .weight(nextReps)
+        .build();
+    
+    totalReps += nextReps;
+    totalNanos += nanos;
+    return ImmutableSet.of(measurement);
+  }
+  
+  abstract long invokeTimeMethod(long reps) throws Exception;
+
+  /**
+   * Returns a random number of reps based on a normal distribution around the estimated number of
+   * reps for the timing interval. The distribution used has a standard deviation of one fifth of
+   * the estimated number of reps.
+   */
+  @VisibleForTesting static long calculateTargetReps(long reps, long nanos, long targetNanos,
+      double gaussian) {
+    double targetReps = (((double) reps) / nanos) * targetNanos;
+    return Math.max(1L, Math.round((gaussian * (targetReps / 5)) + targetReps));
+  }
+
+  /**
+   * A {@link Worker} for micro benchmarks.
+   */
+  public static final class Micro extends RuntimeWorker {
+    @Inject Micro(@Benchmark Object benchmark, 
+        @BenchmarkMethod Method method, Random random, Ticker ticker,
+        @WorkerOptions Map<String, String> workerOptions) {
+      super(benchmark, method, random, ticker, workerOptions);
+    }
+
+    @Override long invokeTimeMethod(long reps) throws Exception {
+      int intReps = (int) reps;
+      if (reps != intReps) {
+        throw new InvalidBenchmarkException("%s.%s takes an int for reps, "
+            + "but requires a greater number to fill the given timing interval (%s). "
+            + "If this is expected (the benchmarked code is very fast), use a long parameter."
+            + "Otherwise, check your benchmark for errors.",
+                benchmark.getClass(), benchmarkMethod.getName(),
+                    ShortDuration.of(options.timingIntervalNanos, NANOSECONDS));
+      }
+      long before = ticker.read();
+      benchmarkMethod.invoke(benchmark, intReps);
+      return ticker.read() - before;
+    }
+  }
+
+  /**
+   * A {@link Worker} for pico benchmarks.
+   */
+  public static final class Pico extends RuntimeWorker {
+    @Inject Pico(@Benchmark Object benchmark, 
+        @BenchmarkMethod Method method, Random random, Ticker ticker,
+        @WorkerOptions Map<String, String> workerOptions) {
+      super(benchmark, method, random, ticker, workerOptions);
+    }
+    
+    @Override long invokeTimeMethod(long reps) throws Exception {
+      long before = ticker.read();
+      benchmarkMethod.invoke(benchmark, reps);
+      return ticker.read() - before;
+    }
+  }
+
+  private static final class Options {
+    long timingIntervalNanos;
+    boolean gcBeforeEach;
+
+    Options(Map<String, String> optionMap) {
+      this.timingIntervalNanos = Long.parseLong(optionMap.get("timingIntervalNanos"));
+      this.gcBeforeEach = Boolean.parseBoolean(optionMap.get("gcBeforeEach"));
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/Worker.java b/caliper/src/main/java/com/google/caliper/worker/Worker.java
new file mode 100644
index 0000000..d0f5c08
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/Worker.java
@@ -0,0 +1,77 @@
+/*
+ * 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.worker;
+
+import com.google.caliper.model.Measurement;
+import com.google.caliper.runner.Running.AfterExperimentMethods;
+import com.google.caliper.runner.Running.BeforeExperimentMethods;
+import com.google.common.collect.ImmutableSet;
+
+import java.lang.reflect.Method;
+
+import javax.inject.Inject;
+
+/**
+ * A {@link Worker} collects measurements on behalf of a particular Instrument.
+ */
+public abstract class Worker {
+  @Inject
+  @BeforeExperimentMethods
+  ImmutableSet<Method> beforeExperimentMethods;
+  
+  @Inject
+  @AfterExperimentMethods
+  ImmutableSet<Method> afterExperimentMethods;
+  
+  protected final Method benchmarkMethod;
+  protected final Object benchmark;
+  
+  protected Worker(Object benchmark, Method method) {
+    this.benchmark = benchmark;
+    this.benchmarkMethod = method;
+  }
+
+  /** Initializes the benchmark object. */
+  final void setUpBenchmark() throws Exception {
+    for (Method method : beforeExperimentMethods) {
+      method.invoke(benchmark);
+    }
+  }
+
+  /** Called once before all measurements but after benchmark setup. */
+  public void bootstrap() throws Exception {}
+
+  /**
+   * Called immediately before {@link #measure()}.
+   *
+   * @param inWarmup whether we are in warmup, or taking real measurements. Used by
+   *                 some implementations to skip forcing GC to make warmup faster.
+   */
+  public void preMeasure(boolean inWarmup) throws Exception {}
+
+  /** Called immediately after {@link #measure()}. */
+  public void postMeasure() throws Exception {}
+
+  /** Template method for workers that produce multiple measurements. */
+  public abstract Iterable<Measurement> measure() throws Exception;
+
+  /** Tears down the benchmark object. */
+  final void tearDownBenchmark() throws Exception {
+    for (Method method : afterExperimentMethods) {
+      method.invoke(benchmark);
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/WorkerComponent.java b/caliper/src/main/java/com/google/caliper/worker/WorkerComponent.java
new file mode 100644
index 0000000..40b11da
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/WorkerComponent.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.worker;
+
+import com.google.caliper.bridge.BridgeModule;
+import com.google.caliper.runner.BenchmarkClassModule;
+import com.google.caliper.runner.ExperimentModule;
+import dagger.Component;
+import javax.inject.Singleton;
+
+/**
+ * Creates {@link Worker} for an {@link com.google.caliper.runner.Experiment}.
+ */
+@Singleton
+@Component(modules = {
+    BenchmarkClassModule.class,
+    BridgeModule.class,
+    ExperimentModule.class,
+    WorkerModule.class
+})
+interface WorkerComponent {
+  Worker getWorker();
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/WorkerEventLog.java b/caliper/src/main/java/com/google/caliper/worker/WorkerEventLog.java
new file mode 100644
index 0000000..7b903d4
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/WorkerEventLog.java
@@ -0,0 +1,88 @@
+/*
+ * 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.worker;
+
+import com.google.caliper.bridge.FailureLogMessage;
+import com.google.caliper.bridge.OpenedSocket;
+import com.google.caliper.bridge.ShouldContinueMessage;
+import com.google.caliper.bridge.StartMeasurementLogMessage;
+import com.google.caliper.bridge.StartupAnnounceMessage;
+import com.google.caliper.bridge.StopMeasurementLogMessage;
+import com.google.caliper.bridge.VmPropertiesLogMessage;
+import com.google.caliper.model.Measurement;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.UUID;
+
+/** The worker's interface for communicating with the runner. */
+final class WorkerEventLog implements Closeable {
+  private final OpenedSocket.Writer writer;
+  private final OpenedSocket.Reader reader;
+
+  WorkerEventLog(OpenedSocket socket) {
+    this.writer = socket.writer();
+    this.reader = socket.reader();
+  }
+
+  void notifyWorkerStarted(UUID trialId) throws IOException {
+    writer.write(new StartupAnnounceMessage(trialId));
+    writer.write(new VmPropertiesLogMessage());
+    writer.flush();
+  }
+
+  void notifyBootstrapPhaseStarting() throws IOException {
+    writer.write("Bootstrap phase starting.");
+    writer.flush();
+  }
+
+  void notifyMeasurementPhaseStarting() throws IOException {
+    writer.write("Measurement phase starting (includes warmup and actual measurement).");
+    writer.flush();
+  }
+
+  void notifyMeasurementStarting() throws IOException {
+    writer.write("About to measure.");
+    writer.write(new StartMeasurementLogMessage());
+    writer.flush();
+  }
+
+  /**
+   * Report the measurements and wait for it to be ack'd by the runner. Returns a message received
+   * from the runner, which lets us know whether to continue measuring and whether we're in the
+   * warmup or measurement phase.
+   */
+  ShouldContinueMessage notifyMeasurementEnding(Iterable<Measurement> measurements) throws
+      IOException {
+    writer.write(new StopMeasurementLogMessage(measurements));
+    writer.flush();
+    return (ShouldContinueMessage) reader.read();
+  }
+
+  void notifyFailure(Exception e) throws IOException {
+    writer.write(new FailureLogMessage(e));
+    writer.flush();
+  }
+
+  @Override public void close() throws IOException {
+    try {
+      reader.close();
+    } finally {
+      writer.close();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/WorkerMain.java b/caliper/src/main/java/com/google/caliper/worker/WorkerMain.java
new file mode 100644
index 0000000..34ec674
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/WorkerMain.java
@@ -0,0 +1,80 @@
+/*
+ * 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.worker;
+
+import com.google.caliper.bridge.CommandLineSerializer;
+import com.google.caliper.bridge.OpenedSocket;
+import com.google.caliper.bridge.ShouldContinueMessage;
+import com.google.caliper.bridge.WorkerSpec;
+import com.google.caliper.runner.ExperimentModule;
+import com.google.common.net.InetAddresses;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.SocketChannel;
+
+/**
+ * This class is invoked as a subprocess by the Caliper runner parent process; it re-stages
+ * the benchmark and hands it off to the instrument's worker.
+ */
+public final class WorkerMain {
+  private WorkerMain() {}
+
+  public static void main(String[] args) throws Exception {
+    // TODO(lukes): instead of parsing the spec from the command line pass the port number on the
+    // command line and then receive the spec from the socket.  This way we can start JVMs prior
+    // to starting experiments and thus get better experiment latency.
+    WorkerSpec request = CommandLineSerializer.parse(args[0]);
+    // nonblocking connect so we can interleave the system call with injector creation.
+    SocketChannel channel = SocketChannel.open();
+    channel.configureBlocking(false);
+    channel.connect(new InetSocketAddress(InetAddresses.forString("127.0.0.1"), request.port));
+
+    WorkerComponent workerComponent = DaggerWorkerComponent.builder()
+        .experimentModule(ExperimentModule.forWorkerSpec(request))
+        .workerModule(new WorkerModule(request))
+        .build();
+    Worker worker = workerComponent.getWorker();
+    WorkerEventLog log = new WorkerEventLog(OpenedSocket.fromSocket(channel));
+
+    log.notifyWorkerStarted(request.trialId);
+    try {
+      worker.setUpBenchmark();
+      log.notifyBootstrapPhaseStarting();
+      worker.bootstrap();
+      log.notifyMeasurementPhaseStarting();
+      boolean keepMeasuring = true;
+      boolean isInWarmup = true;
+      while (keepMeasuring) {
+        worker.preMeasure(isInWarmup);
+        log.notifyMeasurementStarting();
+        try {
+          ShouldContinueMessage message = log.notifyMeasurementEnding(worker.measure());
+          keepMeasuring = message.shouldContinue();
+          isInWarmup = !message.isWarmupComplete();
+        } finally {
+          worker.postMeasure();
+        }
+      }
+    } catch (Exception e) {
+      log.notifyFailure(e);
+    } finally {
+      System.out.flush(); // ?
+      worker.tearDownBenchmark();
+      log.close();
+    }
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/WorkerModule.java b/caliper/src/main/java/com/google/caliper/worker/WorkerModule.java
new file mode 100644
index 0000000..1f7e201
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/WorkerModule.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2012 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.worker;
+
+import com.google.caliper.Param;
+import com.google.caliper.bridge.WorkerSpec;
+import com.google.caliper.runner.Running;
+import com.google.caliper.util.InvalidCommandException;
+import com.google.caliper.util.Util;
+import com.google.common.base.Ticker;
+import com.google.common.collect.ImmutableMap;
+import dagger.MapKey;
+import dagger.Module;
+import dagger.Provides;
+import dagger.Provides.Type;
+
+import java.util.Map;
+import java.util.Random;
+
+import javax.inject.Provider;
+
+/**
+ * Binds classes necessary for the worker. Also manages the injection of {@link Param parameters}
+ * from the {@link WorkerSpec} into the benchmark.
+ *
+ * <p>TODO(gak): Ensure that each worker only has bindings for the objects it needs and not the
+ * objects required by different workers. (i.e. don't bind a Ticker if the worker is an allocation
+ * worker).
+ */
+@Module
+final class WorkerModule {
+  private final Class<? extends Worker> workerClass;
+  private final ImmutableMap<String, String> workerOptions;
+
+  private final Class<?> benchmarkClassObject;
+
+  WorkerModule(WorkerSpec workerSpec) throws ClassNotFoundException {
+    this.workerClass = workerSpec.workerClass.asSubclass(Worker.class);
+    this.workerOptions = workerSpec.workerOptions;
+
+    benchmarkClassObject = Util.loadClass(workerSpec.benchmarkSpec.className());
+  }
+
+  @Provides
+  @Running.BenchmarkClass
+  Class<?> provideBenchmarkClassObject() {
+    return benchmarkClassObject;
+  }
+
+  @Provides
+  Worker provideWorker(Map<Class<? extends Worker>, Provider<Worker>> availableWorkers) {
+    Provider<Worker> workerProvider = availableWorkers.get(workerClass);
+    if (workerProvider == null) {
+      throw new InvalidCommandException("%s is not a supported worker (%s).",
+          workerClass, availableWorkers);
+    }
+    return workerProvider.get();
+  }
+
+  /**
+   * Specifies the {@link Class} object to use as a key in the map of available
+   * {@link Worker workers} passed to {@link #provideWorker(Map)}.
+   */
+  @MapKey(unwrapValue = true)
+  public @interface WorkerClassKey {
+    Class<? extends Worker> value();
+  }
+
+  @Provides(type = Type.MAP)
+  @WorkerClassKey(ArbitraryMeasurementWorker.class)
+  static Worker provideArbitraryMeasurementWorker(ArbitraryMeasurementWorker impl) {
+    return impl;
+  }
+
+  @Provides(type = Type.MAP)
+  @WorkerClassKey(MicrobenchmarkAllocationWorker.class)
+  static Worker provideMicrobenchmarkAllocationWorker(MicrobenchmarkAllocationWorker impl) {
+    return impl;
+  }
+
+  @Provides(type = Type.MAP)
+  @WorkerClassKey(MacrobenchmarkWorker.class)
+  static Worker provideMacrobenchmarkWorker(MacrobenchmarkWorker impl) {
+    return impl;
+  }
+
+  @Provides(type = Type.MAP)
+  @WorkerClassKey(MacrobenchmarkAllocationWorker.class)
+  static Worker provideMacrobenchmarkAllocationWorker(MacrobenchmarkAllocationWorker impl) {
+    return impl;
+  }
+
+  @Provides(type = Type.MAP)
+  @WorkerClassKey(RuntimeWorker.Micro.class)
+  static Worker provideRuntimeWorkerMicro(RuntimeWorker.Micro impl) {
+    return impl;
+  }
+
+  @Provides(type = Type.MAP)
+  @WorkerClassKey(RuntimeWorker.Pico.class)
+  static Worker provideRuntimeWorkerPico(RuntimeWorker.Pico impl) {
+    return impl;
+  }
+
+  @Provides
+  static Ticker provideTicker() {
+    return Ticker.systemTicker();
+  }
+
+  @Provides
+  AllocationRecorder provideAllocationRecorder(
+      Provider<AllAllocationsRecorder> allAllocationsRecorderProvider,
+      Provider<AggregateAllocationsRecorder> aggregateAllocationsRecorderProvider) {
+
+    return Boolean.valueOf(workerOptions.get("trackAllocations"))
+        ? allAllocationsRecorderProvider.get()
+        : aggregateAllocationsRecorderProvider.get();
+  }
+
+  @Provides
+  static Random provideRandom() {
+    return new Random();
+  }
+
+  @Provides
+  @WorkerOptions
+  Map<String, String> provideWorkerOptions() {
+    return workerOptions;
+  }
+}
diff --git a/caliper/src/main/java/com/google/caliper/worker/WorkerOptions.java b/caliper/src/main/java/com/google/caliper/worker/WorkerOptions.java
new file mode 100644
index 0000000..bc93b18
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/worker/WorkerOptions.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 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.worker;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, METHOD})
+@Qualifier
+@interface WorkerOptions {}
diff --git a/caliper/src/main/resources/com/google/caliper/config/default-config.properties b/caliper/src/main/resources/com/google/caliper/config/default-config.properties
new file mode 100644
index 0000000..ee5b3ee
--- /dev/null
+++ b/caliper/src/main/resources/com/google/caliper/config/default-config.properties
@@ -0,0 +1,14 @@
+# Caliper config file
+# Run with --print-config to see all of the options being applied
+
+# INSTRUMENT CONFIG
+# instrument.micro.options.warmup=10s
+# instrument.micro.options.timingInterval=500ms
+# instrument.micro.options.reportedIntervals=7
+# instrument.micro.options.maxRuntime=10s
+
+# VM CONFIG
+vm.args=-Xmx3g -Xms3g
+
+# See the Caliper webapp to get a key so you can associate results with your account
+results.upload.options.key=
diff --git a/caliper/src/main/resources/com/google/caliper/config/global-config.properties b/caliper/src/main/resources/com/google/caliper/config/global-config.properties
new file mode 100644
index 0000000..dd741f0
--- /dev/null
+++ b/caliper/src/main/resources/com/google/caliper/config/global-config.properties
@@ -0,0 +1,90 @@
+# Caliper global config file
+# Users' ~/.caliper/config settings may override these
+
+######################
+# VM CONFIGURATION
+######################
+
+# This directory can be automatically prepended to non-absolute VM paths
+vm.baseDirectory=/usr/local/buildtools/java
+
+# Standard vm parameter options.
+vm.args=
+
+# Common configurations
+
+vm.jdk-32-client.home=jdk-32
+vm.jdk-32-client.args=-d32 -client
+
+vm.jdk-32-server.home=jdk-32
+vm.jdk-32-server.args=-d32 -server
+
+vm.jdk-64-compressed.home=jdk-64
+vm.jdk-64-compressed.args=-d64 -XX:+UseCompressedOops
+
+vm.jdk-64-uncompressed.home=jdk-64
+vm.jdk-64-uncompressed.args=-d64 -XX:-UseCompressedOops
+
+
+######################
+# INSTRUMENT CONFIG
+######################
+
+# To define new instrument configurations, provide an "instrument.<name>.class" property
+# pointing to a concrete class that extends com.google.caliper.runner.Instrument, and add
+# whichever other options it supports using "instrument.<name>.<optionName>=<value>".
+
+# Instrument "runtime"
+instrument.runtime.class=com.google.caliper.runner.RuntimeInstrument
+
+# Do not report any measurements from before this minimum time has elapsed
+instrument.runtime.options.warmup=10s
+# Interrupt warmup when it has been running for this much wall-clock time,
+# even if the measured warmup time (above) hasn't been reached. This prevents fast benchmarks
+# with high per-measurement overhead (e.g. long @BeforeRep and @AfterRep methods)
+# from taking too long to warm up.
+instrument.runtime.options.maxWarmupWallTime=10m
+
+# Caliper chooses rep counts such that the total timing interval comes out near this value.
+# Higher values take longer, but are more precise (less vulnerable to fixed costs)
+instrument.runtime.options.timingInterval=500ms
+
+# Caliper ultimately records only the final N measurements, where N is this value.
+instrument.runtime.options.measurements=9
+
+# Run GC before every measurement?
+instrument.runtime.options.gcBeforeEach=true
+
+# Whether or not to make suggestions about whether a benchmark should be a pico/micro/macro
+# benchmark.  Note that this will not effect errors that result from benchmarks that are unable to
+# take proper measurements due to granularity issues.
+instrument.runtime.options.suggestGranularity=true
+
+# Instrument "arbitrary"
+instrument.arbitrary.class=com.google.caliper.runner.ArbitraryMeasurementInstrument
+
+# Run GC before every measurement?
+instrument.arbitrary.options.gcBeforeEach=false
+
+# Instrument "allocation"
+instrument.allocation.class=com.google.caliper.runner.AllocationInstrument
+
+# Track and log a summary of every individual allocation.  This enables better error messages for
+# buggy benchmarks and prints detailed reports of allocation behavior in verbose mode.  N.B. This
+# can increase the memory usage of the allocation worker significantly, so it is not recommended
+# for benchmarks that do a lot of allocation.
+instrument.allocation.options.trackAllocations=false
+
+
+# Sets the maximum number of trials that can run in parallel.
+runner.maxParallelism=2
+
+######################
+# RESULTS PROCESSORS
+######################
+
+results.file.class=com.google.caliper.runner.OutputFileDumper
+
+results.upload.class=com.google.caliper.runner.HttpUploader
+
+results.upload.options.url=https://microbenchmarks.appspot.com/
diff --git a/caliper/src/test/java/com/google/caliper/AllTests.java b/caliper/src/test/java/com/google/caliper/AllTests.java
deleted file mode 100644
index 0e3d1cc..0000000
--- a/caliper/src/test/java/com/google/caliper/AllTests.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 18857ba..0000000
--- a/caliper/src/test/java/com/google/caliper/CaliperTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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
deleted file mode 100644
index cee02b0..0000000
--- a/caliper/src/test/java/com/google/caliper/JsonTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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
deleted file mode 100644
index f6f7cbb..0000000
--- a/caliper/src/test/java/com/google/caliper/MeasurementSetTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 9063261..0000000
--- a/caliper/src/test/java/com/google/caliper/ParameterTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 85e59af..0000000
--- a/caliper/src/test/java/com/google/caliper/WarmupOverflowTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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/caliper/src/test/java/com/google/caliper/bridge/GcLogMessageGenerator.java b/caliper/src/test/java/com/google/caliper/bridge/GcLogMessageGenerator.java
new file mode 100644
index 0000000..292bc94
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/bridge/GcLogMessageGenerator.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 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.bridge;
+
+import com.sun.management.HotSpotDiagnosticMXBean;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+
+import javax.management.MBeanServer;
+
+/**
+ * A simple class that invokes {@link System#gc()} over and over to generate some GC log messages.
+ */
+public final class GcLogMessageGenerator {
+  public static void main(String[] args) throws IOException {
+    checkGcLogging();
+    for (int i = 0; i < 100; i++) {
+      System.gc();
+    }
+  }
+
+  private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";
+
+  private static void checkGcLogging() throws IOException {
+    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+    HotSpotDiagnosticMXBean bean = ManagementFactory.newPlatformMXBeanProxy(
+        server, HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class);
+    if (!bean.getVMOption("PrintGC").getValue().equals(Boolean.TRUE.toString())) {
+      System.err.println("This is only useful if you run with -XX:+PrintGC");
+      System.exit(1);
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/bridge/LogMessageParserTest.java b/caliper/src/test/java/com/google/caliper/bridge/LogMessageParserTest.java
new file mode 100644
index 0000000..46370d5
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/bridge/LogMessageParserTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2013 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.bridge;
+
+import static com.google.caliper.bridge.GcLogMessage.Type.FULL;
+import static com.google.caliper.bridge.GcLogMessage.Type.INCREMENTAL;
+import static com.google.common.base.Charsets.UTF_8;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.caliper.util.ShortDuration;
+import com.google.common.io.Resources;
+
+import dagger.Component;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Tests {@link LogMessageParser}.
+ */
+@RunWith(JUnit4.class)
+
+public class LogMessageParserTest {
+  @Inject LogMessageParser parser;
+
+  @Component(modules = BridgeModule.class)
+  interface LogMessageParserComponent {
+    void inject(LogMessageParserTest test);
+  }
+
+  @Before public void setUp() {
+    DaggerLogMessageParserTest_LogMessageParserComponent.create().inject(this);
+  }
+
+  @Test public void gcPatten_jdk6() throws Exception {
+    List<String> lines = Resources.readLines(
+        Resources.getResource(LogMessageParserTest.class, "jdk6-gc.txt"), UTF_8);
+    for (String line : lines) {
+      assertTrue(parser.parse(line) instanceof GcLogMessage);
+    }
+  }
+
+  @Test public void gcPatten_jdk7() throws Exception {
+    List<String> lines = Resources.readLines(
+        Resources.getResource(LogMessageParserTest.class, "jdk7-gc.txt"), UTF_8);
+    for (String line : lines) {
+      assertTrue(parser.parse(line) instanceof GcLogMessage);
+    }
+  }
+
+  @Test public void gcMessageData() {
+    assertEquals(new GcLogMessage(INCREMENTAL, ShortDuration.of(1232, MICROSECONDS)),
+        parser.parse("[GC 987K->384K(62848K), 0.0012320 secs]"));
+    assertEquals(new GcLogMessage(FULL, ShortDuration.of(5455, MICROSECONDS)),
+        parser.parse("[Full GC 384K->288K(62848K), 0.0054550 secs]"));
+    assertEquals(new GcLogMessage(INCREMENTAL, ShortDuration.of(1424, MICROSECONDS)),
+        parser.parse(
+            "2013-02-11T20:15:26.706-0600: 0.098: [GC 1316K->576K(62848K), 0.0014240 secs]"));
+    assertEquals(new GcLogMessage(FULL, ShortDuration.of(4486, MICROSECONDS)),
+        parser.parse(
+            "2013-02-11T20:15:26.708-0600: 0.099: [Full GC 576K->486K(62848K), 0.0044860 secs]"));
+  }
+
+  @Test public void jitPattern_jdk6() throws Exception {
+    List<String> lines = Resources.readLines(
+        Resources.getResource(LogMessageParserTest.class, "jdk6-compilation.txt"), UTF_8);
+    for (String line : lines) {
+      assertTrue(parser.parse(line) instanceof HotspotLogMessage);
+    }
+  }
+
+  @Test public void jitPattern_jdk7() throws Exception {
+    List<String> lines = Resources.readLines(
+        Resources.getResource(LogMessageParserTest.class, "jdk7-compilation.txt"), UTF_8);
+    for (String line : lines) {
+      assertTrue(parser.parse(line) instanceof HotspotLogMessage);
+    }
+  }
+
+  @Test public void vmOptionPattern_jdk6() throws Exception {
+    List<String> lines = Resources.readLines(
+        Resources.getResource(LogMessageParserTest.class, "jdk6-flags.txt"), UTF_8);
+    for (String line : lines) {
+      assertTrue(parser.parse(line) instanceof VmOptionLogMessage);
+    }
+  }
+
+  @Test public void vmOptionPattern_jdk7() throws Exception {
+    List<String> lines = Resources.readLines(
+        Resources.getResource(LogMessageParserTest.class, "jdk7-flags.txt"), UTF_8);
+    for (String line : lines) {
+      assertTrue(parser.parse(line) instanceof VmOptionLogMessage);
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/config/CaliperConfigLoaderTest.java b/caliper/src/test/java/com/google/caliper/config/CaliperConfigLoaderTest.java
new file mode 100644
index 0000000..317d1e5
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/config/CaliperConfigLoaderTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 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.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import com.google.caliper.options.CaliperOptions;
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Tests {@link CaliperConfigLoader}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+
+public class CaliperConfigLoaderTest {
+  @Mock CaliperOptions optionsMock;
+
+  private File tempConfigFile;
+
+  @Before public void createTempUserProperties() throws IOException {
+    tempConfigFile = File.createTempFile("caliper-config-test", "properties");
+    tempConfigFile.deleteOnExit();
+    Properties userProperties = new Properties();
+    userProperties.put("some.property", "franklin");
+    FileOutputStream fs = new FileOutputStream(tempConfigFile);
+    userProperties.store(fs, null);
+    fs.close();
+  }
+
+  @After public void deleteTempUserProperties() {
+    tempConfigFile.delete();
+  }
+
+  @Test public void loadOrCreate_configFileExistsNoOverride() throws Exception {
+    when(optionsMock.caliperConfigFile()).thenReturn(tempConfigFile);
+    when(optionsMock.configProperties()).thenReturn(ImmutableMap.<String, String>of());
+    CaliperConfigLoader loader = new CaliperConfigLoader(optionsMock);
+    CaliperConfig config = loader.loadOrCreate();
+    assertEquals("franklin", config.properties.get("some.property"));
+  }
+
+  @Test public void loadOrCreate_configFileExistsWithOverride() throws Exception {
+    when(optionsMock.caliperConfigFile()).thenReturn(tempConfigFile);
+    when(optionsMock.configProperties()).thenReturn(ImmutableMap.of(
+        "some.property", "tacos"));
+    CaliperConfigLoader loader = new CaliperConfigLoader(optionsMock);
+    CaliperConfig config = loader.loadOrCreate();
+    assertEquals("tacos", config.properties.get("some.property"));
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/config/CaliperConfigTest.java b/caliper/src/test/java/com/google/caliper/config/CaliperConfigTest.java
new file mode 100644
index 0000000..05bbe32
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/config/CaliperConfigTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2012 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.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.caliper.api.ResultProcessor;
+import com.google.caliper.model.Trial;
+import com.google.caliper.platform.Platform;
+import com.google.caliper.platform.jvm.JvmPlatform;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+
+/**
+ * Tests {@link CaliperConfig}.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+@RunWith(JUnit4.class)
+public class CaliperConfigTest {
+  @Rule public TemporaryFolder folder = new TemporaryFolder();
+
+  private Platform platform = new JvmPlatform();
+
+  @Test public void getDefaultVmConfig() throws Exception {
+    CaliperConfig configuration = new CaliperConfig(
+        ImmutableMap.of("vm.args", "-very -special=args"));
+    VmConfig defaultVmConfig = configuration.getDefaultVmConfig(platform);
+    assertEquals(new File(System.getProperty("java.home")), defaultVmConfig.vmHome());
+    ImmutableList<String> expectedArgs = new ImmutableList.Builder<String>()
+        .addAll(ManagementFactory.getRuntimeMXBean().getInputArguments())
+        .add("-very")
+        .add("-special=args")
+        .build();
+    assertEquals(expectedArgs, defaultVmConfig.options());
+  }
+
+  @Test public void getVmConfig_baseDirectoryAndName() throws Exception {
+    File tempBaseDir = folder.newFolder();
+    File jdkHome = new File(tempBaseDir, "test");
+    jdkHome.mkdir();
+    CaliperConfig configuration = new CaliperConfig(ImmutableMap.of(
+        "vm.baseDirectory", tempBaseDir.getAbsolutePath()));
+    assertEquals(new VmConfig.Builder(platform, jdkHome).build(),
+        configuration.getVmConfig(platform, "test"));
+  }
+
+  @Test public void getVmConfig_baseDirectoryAndHome() throws Exception {
+    File tempBaseDir = folder.newFolder();
+    File jdkHome = new File(tempBaseDir, "test-home");
+    jdkHome.mkdir();
+    CaliperConfig configuration = new CaliperConfig(ImmutableMap.of(
+        "vm.baseDirectory", tempBaseDir.getAbsolutePath(),
+        "vm.test.home", "test-home"));
+    assertEquals(new VmConfig.Builder(platform, jdkHome).build(),
+        configuration.getVmConfig(platform, "test"));
+  }
+
+  @Test public void getVmConfig() throws Exception {
+    File jdkHome = folder.newFolder();
+    CaliperConfig configuration = new CaliperConfig(ImmutableMap.of(
+        "vm.args", "-a -b   -c",
+        "vm.test.home", jdkHome.getAbsolutePath(),
+        "vm.test.args", " -d     -e     "));
+    assertEquals(
+        new VmConfig.Builder(platform, jdkHome)
+            .addOption("-a")
+            .addOption("-b")
+            .addOption("-c")
+            .addOption("-d")
+            .addOption("-e")
+            .build(),
+        configuration.getVmConfig(platform, "test"));
+  }
+
+  @Test public void getVmConfig_escapedSpacesInArgs() throws Exception {
+    File jdkHome = folder.newFolder();
+    CaliperConfig configuration = new CaliperConfig(ImmutableMap.of(
+        "vm.args", "-a=string\\ with\\ spa\\ces -b -c",
+        "vm.test.home", jdkHome.getAbsolutePath()));
+    assertEquals(
+        new VmConfig.Builder(platform, jdkHome)
+        .addOption("-a=string with spaces")
+        .addOption("-b")
+        .addOption("-c")
+        .build(),
+        configuration.getVmConfig(platform, "test"));
+  }
+
+  @Test public void getInstrumentConfig() throws Exception {
+    CaliperConfig configuration = new CaliperConfig(ImmutableMap.of(
+        "instrument.test.class", "test.ClassName",
+        "instrument.test.options.a", "1",
+        "instrument.test.options.b", "excited b b excited"));
+    assertEquals(
+        new InstrumentConfig.Builder()
+            .className("test.ClassName")
+            .addOption("a", "1")
+            .addOption("b", "excited b b excited")
+            .build(),
+        configuration.getInstrumentConfig("test"));
+  }
+
+  @Test public void getInstrumentConfig_notConfigured() throws Exception {
+    CaliperConfig configuration = new CaliperConfig(ImmutableMap.of(
+        "instrument.test.options.a", "1",
+        "instrument.test.options.b", "excited b b excited"));
+    try {
+      configuration.getInstrumentConfig("test");
+      fail();
+    } catch (IllegalArgumentException expected) {}
+  }
+
+  @Test public void getConfiguredInstruments() throws Exception {
+    CaliperConfig configuration = new CaliperConfig(ImmutableMap.of(
+        "instrument.test.class", "test.ClassName",
+        "instrument.test2.class", "test.ClassName",
+        "instrument.test3.options.a", "1",
+        "instrument.test4.class", "test.ClassName",
+        "instrument.test4.options.b", "excited b b excited"));
+    assertEquals(ImmutableSet.of("test", "test2", "test4"),
+        configuration.getConfiguredInstruments());
+  }
+
+  @Test public void getConfiguredResultProcessors() throws Exception {
+    assertEquals(ImmutableSet.of(),
+        new CaliperConfig(ImmutableMap.<String, String>of()).getConfiguredResultProcessors());
+    CaliperConfig configuration = new CaliperConfig(ImmutableMap.of(
+        "results.test.class", TestResultProcessor.class.getName()));
+    assertEquals(ImmutableSet.of(TestResultProcessor.class),
+        configuration.getConfiguredResultProcessors());
+  }
+
+  @Test public void getResultProcessorConfig() throws Exception {
+    CaliperConfig configuration = new CaliperConfig(ImmutableMap.of(
+        "results.test.class", TestResultProcessor.class.getName(),
+        "results.test.options.g", "ak",
+        "results.test.options.c", "aliper"));
+    assertEquals(
+        new ResultProcessorConfig.Builder()
+            .className(TestResultProcessor.class.getName())
+            .addOption("g", "ak")
+            .addOption("c", "aliper")
+            .build(),
+        configuration.getResultProcessorConfig(TestResultProcessor.class));
+  }
+
+  private static final class TestResultProcessor implements ResultProcessor {
+    @Override public void close() {}
+
+    @Override public void processTrial(Trial trial) {}
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/config/LoggingConfigLoaderTest.java b/caliper/src/test/java/com/google/caliper/config/LoggingConfigLoaderTest.java
new file mode 100644
index 0000000..e4320ef
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/config/LoggingConfigLoaderTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 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.config;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static java.util.logging.Level.INFO;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.caliper.model.Run;
+import com.google.common.io.Files;
+
+import org.joda.time.Instant;
+import org.joda.time.format.ISODateTimeFormat;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.UUID;
+import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+
+/**
+ * Tests {@link LoggingConfigLoader}.
+ */
+
+@RunWith(MockitoJUnitRunner.class)
+public class LoggingConfigLoaderTest {
+  @Rule public TemporaryFolder folder = new TemporaryFolder();
+
+  @Mock LogManager logManager;
+  @Mock Logger logger;
+  @Captor ArgumentCaptor<Handler> handlerCaptor;
+
+  private LoggingConfigLoader loader;
+  private UUID runId = UUID.randomUUID();
+  private Instant startTime = new Instant();
+  private File caliperDirectory;
+
+  @Before public void setUp() throws IOException {
+    this.caliperDirectory = folder.newFolder();
+    this.loader = new LoggingConfigLoader(caliperDirectory, logManager, new Run.Builder(runId)
+        .label("fake run")
+        .startTime(startTime)
+        .build());
+  }
+
+  @Test public void testLoadDefaultLogConfiguration()
+      throws SecurityException, IOException {
+    when(logManager.getLogger("")).thenReturn(logger);
+    loader.maybeLoadDefaultLogConfiguration(logManager);
+    verify(logManager).reset();
+    verify(logger).addHandler(handlerCaptor.capture());
+    FileHandler fileHandler = (FileHandler) handlerCaptor.getValue();
+    assertEquals(UTF_8.name(), fileHandler.getEncoding());
+    assertTrue(fileHandler.getFormatter() instanceof SimpleFormatter);
+    fileHandler.publish(new LogRecord(INFO, "some message"));
+    File logFile = new File(new File(caliperDirectory, "log"),
+        ISODateTimeFormat.basicDateTimeNoMillis().print(startTime) + "." + runId + ".log");
+    assertTrue(logFile.isFile());
+    assertTrue(Files.toString(logFile, UTF_8).contains("some message"));
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/config/VmConfigTest.java b/caliper/src/test/java/com/google/caliper/config/VmConfigTest.java
new file mode 100644
index 0000000..7998866
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/config/VmConfigTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012 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.config;
+
+import static org.junit.Assert.assertTrue;
+
+import com.google.caliper.platform.jvm.JvmPlatform;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+/**
+ * Tests {@link VmConfig}.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+@RunWith(JUnit4.class)
+public class VmConfigTest {
+
+
+  @Test
+  public void testExecutable() {
+    File javaExecutable =
+        new VmConfig.Builder(new JvmPlatform(), new File(System.getProperty("java.home")))
+        .build()
+        .vmExecutable();
+    assertTrue("Could not find: " + javaExecutable, javaExecutable.exists());
+    assertTrue(javaExecutable + " is not a file", javaExecutable.isFile());
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/memory/ObjectGraphMeasurerTest.java b/caliper/src/test/java/com/google/caliper/memory/ObjectGraphMeasurerTest.java
new file mode 100644
index 0000000..5888f9c
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/memory/ObjectGraphMeasurerTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.memory;
+
+import com.google.caliper.memory.ObjectGraphMeasurer.Footprint;
+import com.google.common.collect.ImmutableMultiset;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for ObjectGraphMeasurer.
+ */
+@RunWith(JUnit4.class)
+public class ObjectGraphMeasurerTest extends TestCase {
+  enum DummyEnum {
+    VALUE;
+  }
+  static final Object oneEnumField = new Object() {
+    @SuppressWarnings("unused") DummyEnum enumField = DummyEnum.VALUE;
+  };
+
+  // enums are treated as statics (and ignored)
+  @Test public void testEnum() {
+    ObjectGraphMeasurer.Footprint footprint = ObjectGraphMeasurer.measure(oneEnumField);
+    assertEquals(new Footprint(1, 1, 0, NO_PRIMITIVES), footprint);
+  }
+
+  static final Object oneClassField = new Object() {
+    @SuppressWarnings("unused") Class<?> clazz = Object.class;
+  };
+
+  // Class instances are treated as statics (and ignored)
+  @Test public void testClass() {
+    ObjectGraphMeasurer.Footprint footprint = ObjectGraphMeasurer.measure(oneClassField);
+    assertEquals(new ObjectGraphMeasurer.Footprint(1, 1, 0, NO_PRIMITIVES), footprint);
+  }
+
+  static final Object oneObjectField = new Object() {
+    @SuppressWarnings("unused") Object objectField = new Object();
+  };
+
+  @Test public void testObject() {
+    ObjectGraphMeasurer.Footprint footprint = ObjectGraphMeasurer.measure(oneObjectField);
+    assertEquals(new ObjectGraphMeasurer.Footprint(2, 1, 0, NO_PRIMITIVES), footprint);
+  }
+
+  static final Object withCycle = new Object() {
+    Object[] array = new Object[1];
+    {
+      array[0] = this;
+    }
+  };
+
+  @Test public void testCycle() {
+    ObjectGraphMeasurer.Footprint footprint = ObjectGraphMeasurer.measure(withCycle);
+    assertEquals(new ObjectGraphMeasurer.Footprint(2, 2, 0, NO_PRIMITIVES), footprint);
+  }
+
+  static final Object multiplePathsToObject = new Object() {
+    Object object = new Object();
+    @SuppressWarnings("unused") Object ref1 = object;
+    @SuppressWarnings("unused") Object ref2 = object;
+  };
+
+  @Test public void testMultiplePathsToObject() {
+    ObjectGraphMeasurer.Footprint footprint = ObjectGraphMeasurer.measure(multiplePathsToObject);
+    assertEquals(new ObjectGraphMeasurer.Footprint(2, 3, 0, NO_PRIMITIVES), footprint);
+  }
+
+  static final Object multiplePathsToClass = new Object() {
+    Object object = Object.class;
+    @SuppressWarnings("unused") Object ref1 = object;
+    @SuppressWarnings("unused") Object ref2 = object;
+  };
+
+  @Test public void testMultiplePathsToClass() {
+    ObjectGraphMeasurer.Footprint footprint = ObjectGraphMeasurer.measure(multiplePathsToClass);
+    assertEquals(new ObjectGraphMeasurer.Footprint(1, 3, 0, NO_PRIMITIVES), footprint);
+  }
+
+  static class WithStaticField {
+    static WithStaticField INSTANCE = new WithStaticField();
+  }
+
+  @Test public void testStaticFields() {
+    ObjectGraphMeasurer.Footprint footprint = ObjectGraphMeasurer.measure(new WithStaticField());
+    assertEquals(new ObjectGraphMeasurer.Footprint(1, 0, 0, NO_PRIMITIVES), footprint);
+  }
+
+  @SuppressWarnings("unused") // unused test fields
+  static final Object oneNullOneNonNull = new Object() {
+    Object nonNull1 = new Object();
+    Object nonNull2 = nonNull1;
+    Object null1 = null;
+    Object null2 = null;
+    Object null3 = null;
+  };
+
+  @Test public void testNullField() {
+    ObjectGraphMeasurer.Footprint footprint = ObjectGraphMeasurer.measure(oneNullOneNonNull);
+    assertEquals(new ObjectGraphMeasurer.Footprint(2, 2, 3, NO_PRIMITIVES), footprint);
+  }
+
+  private static final ImmutableMultiset<Class<?>> NO_PRIMITIVES = ImmutableMultiset.of();
+}
diff --git a/caliper/src/test/java/com/google/caliper/options/ParsedOptionsTest.java b/caliper/src/test/java/com/google/caliper/options/ParsedOptionsTest.java
new file mode 100644
index 0000000..46d678a
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/options/ParsedOptionsTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.options;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.caliper.util.DisplayUsageException;
+import com.google.caliper.util.InvalidCommandException;
+import com.google.caliper.util.ShortDuration;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.IOException;
+
+@RunWith(JUnit4.class)
+
+public class ParsedOptionsTest {
+  private File tempDir;
+
+  @Before public void setUp() throws IOException {
+    tempDir = Files.createTempDir();
+    makeTestVmTree(tempDir);
+  }
+
+  @After public void tearDown() throws IOException {
+    if (tempDir != null) {
+      Runtime.getRuntime().exec(new String[] {"rm", "-rf", tempDir.getCanonicalPath()});
+    }
+  }
+
+  private static void makeTestVmTree(File baseDir) throws IOException {
+    File bin = new File(baseDir, "testVm/bin");
+    bin.mkdirs();
+    File java = new File(bin, "java");
+    Files.touch(java);
+  }
+
+  @Test public void testNoOptions_RequireBenchmarkClassName() {
+    try {
+      ParsedOptions.from(new String[] {}, true);
+      fail();
+    } catch (InvalidCommandException expected) {
+      assertEquals("No benchmark class specified", expected.getMessage());
+    }
+  }
+
+  @Test public void testTooManyArguments_RequireBenchmarkClassName() {
+    try {
+      ParsedOptions.from(new String[] {"a", "b"}, true);
+      fail();
+    } catch (InvalidCommandException expected) {
+      assertEquals("Extra stuff, expected only class name: [a, b]", expected.getMessage());
+    }
+  }
+
+  @Test public void testTooManyArguments_DoNotRequireBenchmarkClassName() {
+    try {
+      ParsedOptions.from(new String[] {"a", "b"}, false);
+      fail();
+    } catch (InvalidCommandException expected) {
+      assertEquals("Extra stuff, did not expect non-option arguments: [a, b]",
+          expected.getMessage());
+    }
+  }
+
+  @Test public void testHelp() throws InvalidCommandException {
+    try {
+      ParsedOptions.from(new String[] {"--help"}, true);
+      fail();
+    } catch (DisplayUsageException expected) {
+    }
+  }
+
+  @Test public void testDefaults_RequireBenchmarkClassName() throws InvalidCommandException {
+    CaliperOptions options = ParsedOptions.from(new String[] {CLASS_NAME}, true);
+
+    assertEquals(CLASS_NAME, options.benchmarkClassName());
+    checkDefaults(options);
+  }
+
+  @Test public void testDefaults_DoNotRequireBenchmarkClassName() throws InvalidCommandException {
+    CaliperOptions options = ParsedOptions.from(new String[] {}, false);
+
+    assertNull(options.benchmarkClassName());
+    checkDefaults(options);
+  }
+
+  private void checkDefaults(CaliperOptions options) {
+    assertTrue(options.benchmarkMethodNames().isEmpty());
+    assertFalse(options.dryRun());
+    ImmutableSet<String> expectedInstruments = new ImmutableSet.Builder<String>()
+        .add("allocation")
+        .add("runtime")
+        .build();
+    assertEquals(expectedInstruments, options.instrumentNames());
+    assertEquals(1, options.trialsPerScenario());
+    assertTrue(options.userParameters().isEmpty());
+    assertFalse(options.printConfiguration());
+    assertTrue(options.vmArguments().isEmpty());
+    assertEquals(0, options.vmNames().size());
+  }
+
+  @Test public void testKitchenSink() throws InvalidCommandException {
+    String[] args = {
+        "--benchmark=foo;bar;qux",
+        "--instrument=testInstrument",
+        "--directory=/path/to/some/dir",
+        "--trials=2",
+        "--time-limit=15s",
+        "-Dx=a;b;c",
+        "-Dy=b;d",
+        "-Csome.property=value",
+        "-Csome.other.property=other-value",
+        "--print-config",
+        "-JmemoryMax=-Xmx32m;-Xmx64m",
+        "--vm=testVm",
+        "--delimiter=;",
+        CLASS_NAME,
+    };
+    CaliperOptions options = ParsedOptions.from(args, true);
+
+    assertEquals(CLASS_NAME, options.benchmarkClassName());
+    assertEquals(ImmutableSet.of("foo", "bar", "qux"), options.benchmarkMethodNames());
+    assertFalse(options.dryRun());
+    assertEquals(ImmutableSet.of("testInstrument"), options.instrumentNames());
+    assertEquals(new File("/path/to/some/dir"), options.caliperDirectory());
+    assertEquals(2, options.trialsPerScenario());
+    assertEquals(ShortDuration.of(15, SECONDS), options.timeLimit());
+    assertEquals(ImmutableSetMultimap.of("x", "a", "x", "b", "x", "c", "y", "b", "y", "d"),
+        options.userParameters());
+    assertEquals(ImmutableMap.of("some.property", "value", "some.other.property", "other-value"),
+        options.configProperties());
+    assertTrue(options.printConfiguration());
+    assertEquals(ImmutableSetMultimap.of("memoryMax", "-Xmx32m", "memoryMax", "-Xmx64m"),
+        options.vmArguments());
+
+    String vmName = Iterables.getOnlyElement(options.vmNames());
+    assertEquals("testVm", vmName);
+  }
+
+  public static class FakeBenchmark {}
+
+  private static final String CLASS_NAME = FakeBenchmark.class.getName();
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/AllocationInstrumentTest.java b/caliper/src/test/java/com/google/caliper/runner/AllocationInstrumentTest.java
new file mode 100644
index 0000000..0186aa0
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/AllocationInstrumentTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
+import com.google.caliper.config.VmConfig;
+import com.google.caliper.model.Measurement;
+import com.google.caliper.model.Trial;
+import com.google.caliper.platform.Platform;
+import com.google.caliper.platform.jvm.JvmPlatform;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Tests {@link AllocationInstrument}.
+ */
+
+@RunWith(JUnit4.class)
+public class AllocationInstrumentTest {
+
+  @Rule public CaliperTestWatcher runner = new CaliperTestWatcher();
+
+  @Test public void getExtraCommandLineArgs() throws Exception {
+    AllocationInstrument instrument = new AllocationInstrument();
+    File fakeJar = File.createTempFile("fake", "jar");
+    fakeJar.deleteOnExit();
+    instrument.setOptions(ImmutableMap.of("allocationAgentJar", fakeJar.getAbsolutePath()));
+    ImmutableSet<String> expected = new ImmutableSet.Builder<String>()
+        .addAll(JvmPlatform.INSTRUMENT_JVM_ARGS)
+        .add("-Xint")
+        .add("-javaagent:" + fakeJar.getAbsolutePath())
+        .add("-Xbootclasspath/a:" + fakeJar.getAbsolutePath())
+        .add("-Dsun.reflect.inflationThreshold=0")
+        .build();
+    Platform platform = new JvmPlatform();
+    VmConfig vmConfig = new VmConfig.Builder(platform, new File(System.getProperty("java.home")))
+        .build();
+    assertEquals(expected, instrument.getExtraCommandLineArgs(vmConfig));
+    fakeJar.delete();
+  }
+
+  @Test
+  public void intrinsics() throws Exception {
+    runner.forBenchmark(ArrayListGrowthBenchmark.class)
+        .instrument("allocation")
+        .run();
+    Trial trial = Iterables.getOnlyElement(runner.trials());
+    ImmutableListMultimap<String, Measurement> measurementsByDescription =
+        Measurement.indexByDescription(trial.measurements());
+    // 14 objects and 1960 bytes are the known values for growing an ArrayList from 1 element to 100
+    // elements
+    for (Measurement objectMeasurement : measurementsByDescription.get("objects")) {
+      assertEquals(14.0, objectMeasurement.value().magnitude() / objectMeasurement.weight(), 0.001);
+    }
+    for (Measurement byteMeasurement : measurementsByDescription.get("bytes")) {
+      assertEquals(1960.0, byteMeasurement.value().magnitude() / byteMeasurement.weight(), 0.001);
+    }
+  }
+
+  public static class TestBenchmark {
+    List<Object> list = Lists.newLinkedList();
+    @Benchmark public int compressionSize(int reps) {
+      for (int i = 0; i < reps; i++) {
+        list.add(new Object());
+      }
+      int hashCode = list.hashCode();
+      list.clear();
+      return hashCode;
+    }
+  }
+
+  public static class ArrayListGrowthBenchmark {
+    @BeforeExperiment void warmUp() {
+      // ensure that hotspot has compiled this code
+      benchmarkGrowth(100000);
+    }
+
+    @Benchmark void benchmarkGrowth(int reps) {
+      for (int i = 0; i < reps; i++) {
+        List<String> list = Lists.newArrayListWithCapacity(1);
+        for (int j = 0; j < 100; j++) {
+          list.add("");
+        }
+      }
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/ArbitraryMeasurmentInstrumentTest.java b/caliper/src/test/java/com/google/caliper/runner/ArbitraryMeasurmentInstrumentTest.java
new file mode 100644
index 0000000..1e7f98c
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/ArbitraryMeasurmentInstrumentTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.caliper.model.ArbitraryMeasurement;
+import com.google.caliper.model.Measurement;
+import com.google.caliper.model.Value;
+import com.google.common.collect.Iterables;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Integration tests for the {@link ArbitraryMeasurementInstrument}
+ */
+@RunWith(JUnit4.class)
+public class ArbitraryMeasurmentInstrumentTest {
+  @Rule public CaliperTestWatcher runner = new CaliperTestWatcher();
+
+  @Test
+
+  public void testSuccess() throws Exception {
+    runner.forBenchmark(TestBenchmark.class)
+        .instrument("arbitrary")
+        .run();
+    Measurement measurement = Iterables.getOnlyElement(
+        Iterables.getOnlyElement(runner.trials()).measurements());
+    Measurement expected = new Measurement.Builder()
+        .description("fake measurment")
+        .weight(1)
+        .value(Value.create(1.0, "hz"))
+        .build();
+    assertEquals(expected, measurement);
+  }
+
+  public static class TestBenchmark {
+    @ArbitraryMeasurement(units = "hz", description = "fake measurment")
+    public double compressionSize() {
+      return 1.0;
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/BadUserCodeTest.java b/caliper/src/test/java/com/google/caliper/runner/BadUserCodeTest.java
new file mode 100644
index 0000000..3312088
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/BadUserCodeTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
+import com.google.common.collect.Lists;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+/**
+ * Integration tests for misbehaving benchmarks.
+ */
+@RunWith(JUnit4.class)
+public class BadUserCodeTest {
+  @Rule public CaliperTestWatcher runner = new CaliperTestWatcher();
+
+  @Test
+
+  public void testExceptionInInit() throws Exception {
+    try {
+      runner.forBenchmark(ExceptionInInitBenchmark.class).run();
+      fail();
+    } catch (UserCodeException expected) {}
+  }
+
+  private static void throwSomeUserException() {
+    throw new RuntimeException();
+  }
+
+  static class ExceptionInInitBenchmark {
+    static {
+      throwSomeUserException();
+    }
+
+    @Benchmark void timeSomething(int reps) {
+      fail("" + reps);
+    }
+  }
+
+  @Test
+
+  public void testExceptionInConstructor() throws Exception {
+    try {
+      runner.forBenchmark(ExceptionInConstructorBenchmark.class).run();
+      fail();
+    } catch (UserCodeException expected) {}
+  }
+
+  static class ExceptionInConstructorBenchmark {
+    ExceptionInConstructorBenchmark() {
+      throw new RuntimeException();
+    }
+
+    @Benchmark void timeSomething(int reps) {
+      fail("" + reps);
+    }
+  }
+
+  @Test
+
+  public void testExceptionInMethod() throws Exception {
+    try {
+      runner.forBenchmark(ExceptionInMethodBenchmark.class).run();
+      fail();
+    } catch (UserCodeException expected) {}
+  }
+
+  static class ExceptionInMethodBenchmark {
+    @Benchmark void timeSomething(@SuppressWarnings("unused") int reps) {
+      throw new RuntimeException();
+    }
+  }
+
+  @Test
+
+  public void testExceptionInMethod_notInDryRun() throws Exception {
+    try {
+      runner.forBenchmark(ExceptionLateInMethodBenchmark.class).run();
+      fail();
+    } catch (ProxyWorkerException expected) {
+      assertTrue(expected.getMessage().contains(ExceptionLateInMethodBenchmark.class.getName()));
+    }
+  }
+
+  static class ExceptionLateInMethodBenchmark {
+    @Benchmark void timeSomething(int reps) {
+      if (reps > 1) {
+        throw new RuntimeException();
+      }
+    }
+  }
+
+  @Test
+
+  public void testExceptionInSetUp() throws Exception {
+    try {
+      runner.forBenchmark(ExceptionInSetUpBenchmark.class).run();
+      fail();
+    } catch (UserCodeException expected) {}
+  }
+
+  static class ExceptionInSetUpBenchmark {
+    @BeforeExperiment void setUp() {
+      throw new RuntimeException();
+    }
+
+    @Benchmark void timeSomething(int reps) {
+      fail("" + reps);
+    }
+  }
+
+  @Test
+
+  public void testNonDeterministicAllocation_noTrackAllocations() throws Exception {
+    try {
+      runner.forBenchmark(NonDeterministicAllocationBenchmark.class)
+          .instrument("allocation")
+          .options("-Cinstrument.allocation.options.trackAllocations=" + false)
+          .run();
+      fail();
+    } catch (ProxyWorkerException expected) {
+      String message = "Your benchmark appears to have non-deterministic allocation behavior";
+      assertTrue("Expected " + expected.getMessage() + " to contain " + message,
+          expected.getMessage().contains(message));
+    }
+  }
+
+  @Test
+
+  public void testNonDeterministicAllocation_trackAllocations() throws Exception {
+    try {
+      runner.forBenchmark(NonDeterministicAllocationBenchmark.class)
+          .instrument("allocation")
+          .options("-Cinstrument.allocation.options.trackAllocations=" + true)
+          .run();
+      fail();
+    } catch (ProxyWorkerException expected) {
+      String message = "Your benchmark appears to have non-deterministic allocation behavior";
+      assertTrue("Expected " + expected.getMessage() + " to contain " + message,
+          expected.getMessage().contains(message));
+    }
+  }
+
+  /** The number of allocations is non deterministic because it depends on static state. */
+  static class NonDeterministicAllocationBenchmark {
+    static int timeCount = 0;
+    // We dump items into this list so the jit cannot remove the allocations
+    static List<Object> list = Lists.newArrayList();
+    @Benchmark int timeSomethingFBZ(@SuppressWarnings("unused") int reps) {
+      timeCount++;
+      if (timeCount % 2 == 0) {
+        list.add(new Object());
+        return list.hashCode();
+      }
+      return this.hashCode();
+    }
+  }
+
+  @Test
+
+  public void testComplexNonDeterministicAllocation_noTrackAllocations() throws Exception {
+    // Without trackAllocations enabled this kind of non-determinism cannot be detected.
+    runner.forBenchmark(ComplexNonDeterministicAllocationBenchmark.class)
+        .instrument("allocation")
+        .options("-Cinstrument.allocation.options.trackAllocations=" + false)
+        .run();
+  }
+
+  @Test
+
+  public void testComplexNonDeterministicAllocation_trackAllocations() throws Exception {
+    try {
+      runner.forBenchmark(ComplexNonDeterministicAllocationBenchmark.class)
+          .instrument("allocation")
+          .options("-Cinstrument.allocation.options.trackAllocations=" + true)
+          .run();
+    } catch (ProxyWorkerException expected) {
+      String message = "Your benchmark appears to have non-deterministic allocation behavior";
+      assertTrue("Expected " + expected.getMessage() + " to contain " + message,
+          expected.getMessage().contains(message));
+    }
+  }
+
+  /** Benchmark allocates the same number of things each time but in a different way. */
+  static class ComplexNonDeterministicAllocationBenchmark {
+    static int timeCount = 0;
+    @Benchmark int timeSomethingFBZ(@SuppressWarnings("unused") int reps) {
+      // We dump items into this list so the jit cannot remove the allocations
+      List<Object> list = Lists.newArrayList();
+      timeCount++;
+      if (timeCount % 2 == 0) {
+        list.add(new Object());
+      } else {
+        list.add(new Object());
+      }
+      return list.hashCode();
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/BenchmarkClassCheckerTest.java b/caliper/src/test/java/com/google/caliper/runner/BenchmarkClassCheckerTest.java
new file mode 100644
index 0000000..66305d2
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/BenchmarkClassCheckerTest.java
@@ -0,0 +1,63 @@
+package com.google.caliper.runner;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.caliper.Benchmark;
+import com.google.caliper.api.Macrobenchmark;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Tests {@link BenchmarkClassChecker}.
+ */
+
+@RunWith(JUnit4.class)
+public class BenchmarkClassCheckerTest {
+
+  private BenchmarkClassChecker benchmarkClassChecker =
+      BenchmarkClassChecker.create(Collections.<String>emptyList());
+
+  @Test
+  public void testNoBenchmarkMethods() {
+    assertFalse(benchmarkClassChecker.isBenchmark(Object.class));
+  }
+
+  @Test
+  public void testBenchmarkAnnotatedMethod() {
+    assertTrue(benchmarkClassChecker.isBenchmark(BenchmarkAnnotatedMethod.class));
+  }
+
+  public static class BenchmarkAnnotatedMethod {
+    @Benchmark void benchmarkMethod() {}
+  }
+
+  @Test
+  public void testMacroBenchmarkAnnotatedMethod() {
+    assertTrue(benchmarkClassChecker.isBenchmark(MacroBenchmarkAnnotatedMethod.class));
+  }
+
+  @Test
+  public void testMacroBenchmarkAnnotatedMethod_NoSuitableInstrument() {
+    benchmarkClassChecker = BenchmarkClassChecker.create(Arrays.asList("-i", "allocation"));
+    assertFalse(benchmarkClassChecker.isBenchmark(MacroBenchmarkAnnotatedMethod.class));
+  }
+
+  public static class MacroBenchmarkAnnotatedMethod {
+    @Macrobenchmark void macrobenchmarkMethod() {}
+  }
+
+  @Test
+  public void testTimeMethod() {
+    assertTrue(benchmarkClassChecker.isBenchmark(TimeMethod.class));
+  }
+
+  public static class TimeMethod {
+    public void timeMethod() {}
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/BenchmarkClassTest.java b/caliper/src/test/java/com/google/caliper/runner/BenchmarkClassTest.java
new file mode 100644
index 0000000..dcc6113
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/BenchmarkClassTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.caliper.AfterExperiment;
+import com.google.caliper.BeforeExperiment;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests {@link BenchmarkClass}.
+ */
+@RunWith(JUnit4.class)
+public class BenchmarkClassTest {
+  @Test public void beforeMeasurementMethods_AnnotatedBenchmark() throws Exception {
+    assertEquals(
+        ImmutableSet.of(
+            MyBenchmark.class.getDeclaredMethod("before1"),
+            MyBenchmark.class.getDeclaredMethod("before2")),
+        BenchmarkClass.forClass(MyBenchmark.class).beforeExperimentMethods());
+  }
+
+  @Test public void afterMeasurementMethods_AnnotatedBenchmark() throws Exception {
+    assertEquals(
+        ImmutableSet.of(
+            MyBenchmark.class.getDeclaredMethod("after1"),
+            MyBenchmark.class.getDeclaredMethod("after2")),
+        BenchmarkClass.forClass(MyBenchmark.class).afterExperimentMethods());
+  }
+
+  @Test public void forClass_inheritenceThrows() throws Exception {
+    try {
+      BenchmarkClass.forClass(MalformedBenhcmark.class);
+      fail();
+    } catch (InvalidBenchmarkException expected) {}
+  }
+
+  static class MyBenchmark {
+    @BeforeExperiment void before1() {}
+    @BeforeExperiment void before2() {}
+    @AfterExperiment void after1() {}
+    @AfterExperiment void after2() {}
+  }
+
+  static class MalformedBenhcmark extends MyBenchmark {}
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/BenchmarkCreatorTest.java b/caliper/src/test/java/com/google/caliper/runner/BenchmarkCreatorTest.java
new file mode 100644
index 0000000..c1b2bf6
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/BenchmarkCreatorTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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.runner;
+
+import com.google.caliper.Param;
+import com.google.common.collect.ImmutableSortedMap;
+import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for {@link BenchmarkCreator}
+ */
+@RunWith(JUnit4.class)
+public class BenchmarkCreatorTest extends TestCase {
+
+  @Test
+  public void publicDefaultConstructorNoParamBenchmark() {
+    BenchmarkCreator creator = new BenchmarkCreator(PublicDefaultConstructorNoParamBenchmark.class,
+        ImmutableSortedMap.<String, String>of());
+
+    Object benchmarkInstance = creator.createBenchmarkInstance();
+    assertTrue(benchmarkInstance instanceof PublicDefaultConstructorNoParamBenchmark);
+  }
+
+  public static class PublicDefaultConstructorNoParamBenchmark {
+  }
+
+  @Test
+  public void publicDefaultConstructorWithParamBenchmark() {
+    BenchmarkCreator creator = new BenchmarkCreator(
+        PublicDefaultConstructorWithParamBenchmark.class,
+        ImmutableSortedMap.of("byteField", "1", "intField", "2", "stringField", "string"));
+
+    Object benchmarkInstance = creator.createBenchmarkInstance();
+    assertTrue(benchmarkInstance instanceof PublicDefaultConstructorWithParamBenchmark);
+    PublicDefaultConstructorWithParamBenchmark benchmark =
+        (PublicDefaultConstructorWithParamBenchmark) benchmarkInstance;
+    assertEquals(1, benchmark.byteField);
+    assertEquals(2, benchmark.intField);
+    assertEquals("string", benchmark.stringField);
+  }
+
+  public static class PublicDefaultConstructorWithParamBenchmark {
+    @Param
+    byte byteField;
+
+    @Param
+    int intField;
+
+    @Param
+    String stringField;
+  }
+
+  @Test
+  public void publicNoSuitableConstructorBenchmark() {
+    try {
+      new BenchmarkCreator(
+          PublicNoSuitableConstructorBenchmark.class,
+          ImmutableSortedMap.<String, String>of());
+    } catch (UserCodeException e) {
+      assertEquals("Benchmark class "
+          + PublicNoSuitableConstructorBenchmark.class.getName()
+          + " does not have a publicly visible default constructor", e.getMessage());
+    }
+  }
+
+  public static class PublicNoSuitableConstructorBenchmark {
+    @Param
+    byte byteField;
+
+    @Param
+    int intField;
+
+    @Param
+    String stringField;
+
+    public PublicNoSuitableConstructorBenchmark(
+        byte byteField, int intField, String stringField) {
+      this.byteField = byteField;
+      this.intField = intField;
+      this.stringField = stringField;
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/CaliperTestWatcher.java b/caliper/src/test/java/com/google/caliper/runner/CaliperTestWatcher.java
new file mode 100644
index 0000000..63f0044
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/CaliperTestWatcher.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.caliper.config.InvalidConfigurationException;
+import com.google.caliper.model.Trial;
+import com.google.caliper.util.InvalidCommandException;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A {@link TestWatcher} that can be used to configure a run of caliper.
+ * 
+ * <p>Provides common test configuration utilities and redirects output to a buffer and only dumps
+ * it during a failure.
+ * 
+ * <p>TODO(lukes,gak): This is a bad name since it isn't just watching the tests, it is helping you
+ * run the tests.
+ */
+public final class CaliperTestWatcher extends TestWatcher {
+  // N.B. StringWriter is internally synchronized and is safe to write to from multiple threads.
+  private StringWriter stdout;
+  private final StringWriter stderr = new StringWriter();
+  private File workerOutput;
+
+  private String instrument;
+  private Class<?> benchmarkClass;
+  private List<String> extraOptions = Lists.newArrayList();
+
+  CaliperTestWatcher forBenchmark(Class<?> benchmarkClass) {
+    this.benchmarkClass = benchmarkClass;
+    return this;
+  }
+  
+  CaliperTestWatcher instrument(String instrument) {
+    this.instrument = instrument;
+    return this;
+  }
+  
+  CaliperTestWatcher options(String... extraOptions) {
+    this.extraOptions = Arrays.asList(extraOptions);
+    return this;
+  }
+  
+  void run() throws InvalidCommandException, InvalidBenchmarkException, 
+      InvalidConfigurationException {
+    checkState(benchmarkClass != null, "You must configure a benchmark!");
+    workerOutput = Files.createTempDir();
+    // configure a custom dir so the files aren't deleted when CaliperMain returns
+    List<String> options = Lists.newArrayList(
+        "-Cworker.output=" + workerOutput.getPath(),
+        "-Cresults.file.class=",
+        "-Cresults.upload.class=" + InMemoryResultsUploader.class.getName());
+    if (instrument != null) {
+      options.add("-i");
+      options.add(instrument);
+    }
+    options.addAll(extraOptions);
+    options.add(benchmarkClass.getName());
+    this.stdout = new StringWriter();
+    CaliperMain.exitlessMain(
+        options.toArray(new String[0]),
+        new PrintWriter(stdout,  true),
+        new PrintWriter(stderr,  true));
+  }
+
+  @Override protected void finished(Description description) {
+    if (workerOutput != null) {
+      for (File f : workerOutput.listFiles()) {
+        f.delete();
+      }
+      workerOutput.delete();
+    }
+  }
+  
+  @Override protected void failed(Throwable e, Description description) {
+    // don't log if run was never called.
+    if (stdout != null) {
+      System.err.println("Caliper failed with the following output (stdout):\n"
+          + stdout.toString() + "stderr:\n" + stderr.toString());
+    }
+  }
+  
+  ImmutableList<Trial> trials() {
+    return InMemoryResultsUploader.trials();
+  }
+
+  public StringWriter getStderr() {
+    return stderr;
+  }
+  
+  public StringWriter getStdout() {
+    return stdout;
+  }
+  
+  File workerOutputDirectory() {
+    return workerOutput;
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/ExperimentingRunnerModuleTest.java b/caliper/src/test/java/com/google/caliper/runner/ExperimentingRunnerModuleTest.java
new file mode 100644
index 0000000..a941f56
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/ExperimentingRunnerModuleTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+import com.google.caliper.Benchmark;
+import com.google.caliper.options.CaliperOptions;
+import com.google.caliper.platform.Platform;
+import com.google.caliper.platform.SupportedPlatform;
+import com.google.caliper.runner.Instrument.Instrumentation;
+import com.google.caliper.worker.Worker;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.lang.reflect.Method;
+
+/**
+ * Tests {@link ExperimentingRunnerModule}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ExperimentingRunnerModuleTest {
+  private ExperimentingRunnerModule module = new ExperimentingRunnerModule();
+  private Instrument instrumentA = new FakeInstrument();
+  private Instrument instrumentB = new FakeInstrument();
+
+  @Mock CaliperOptions options;
+
+  private Method methodA;
+  private Method methodB;
+  private Method methodC;
+
+  @Before public void setUp() throws Exception {
+    methodA = TestBenchmark.class.getDeclaredMethod("a");
+    methodB = TestBenchmark.class.getDeclaredMethod("b");
+    methodC = TestBenchmark.class.getDeclaredMethod("c");
+  }
+
+  @Test public void provideInstrumentations_noNames() throws Exception {
+    when(options.benchmarkMethodNames()).thenReturn(ImmutableSet.<String>of());
+    assertEquals(
+        new ImmutableSet.Builder<Instrumentation>()
+            .add(instrumentA.createInstrumentation(methodA))
+            .add(instrumentA.createInstrumentation(methodB))
+            .add(instrumentA.createInstrumentation(methodC))
+            .add(instrumentB.createInstrumentation(methodA))
+            .add(instrumentB.createInstrumentation(methodB))
+            .add(instrumentB.createInstrumentation(methodC))
+            .build(),
+        module.provideInstrumentations(options,
+            BenchmarkClass.forClass(TestBenchmark.class),
+            ImmutableSet.of(instrumentA, instrumentB)));
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test public void provideInstrumentations_withNames() throws Exception {
+    when(options.benchmarkMethodNames()).thenReturn(ImmutableSet.of("b"),
+        ImmutableSet.of("a", "c"));
+    assertEquals(
+        new ImmutableSet.Builder<Instrumentation>()
+            .add(instrumentA.createInstrumentation(methodB))
+            .add(instrumentB.createInstrumentation(methodB))
+            .build(),
+        module.provideInstrumentations(options,
+            BenchmarkClass.forClass(TestBenchmark.class),
+            ImmutableSet.of(instrumentA, instrumentB)));
+    assertEquals(
+        new ImmutableSet.Builder<Instrumentation>()
+            .add(instrumentA.createInstrumentation(methodA))
+            .add(instrumentA.createInstrumentation(methodC))
+            .add(instrumentB.createInstrumentation(methodA))
+            .add(instrumentB.createInstrumentation(methodC))
+            .build(),
+        module.provideInstrumentations(options,
+            BenchmarkClass.forClass(TestBenchmark.class),
+            ImmutableSet.of(instrumentA, instrumentB)));
+  }
+
+  @Test public void provideInstrumentations_withInvalidName() {
+    when(options.benchmarkMethodNames()).thenReturn(
+        ImmutableSet.of("a", "c", "bad"));
+    try {
+      module.provideInstrumentations(options,
+          BenchmarkClass.forClass(TestBenchmark.class),
+          ImmutableSet.of(instrumentA, instrumentB));
+      fail("should have thrown for invalid benchmark method name");
+    } catch (Exception expected) {
+      assertTrue(expected.getMessage().contains("[bad]"));
+    }
+  }
+
+  static final class TestBenchmark {
+    @Benchmark void a() {}
+    @Benchmark void b() {}
+    @Benchmark void c() {}
+  }
+
+  @SupportedPlatform(Platform.Type.JVM)
+  static final class FakeInstrument extends Instrument {
+    @Override public boolean isBenchmarkMethod(Method method) {
+      return true;
+    }
+
+    @Override
+    public Instrumentation createInstrumentation(Method benchmarkMethod)
+        throws InvalidBenchmarkException {
+      return new Instrumentation(benchmarkMethod) {
+        @Override
+        public Class<? extends Worker> workerClass() {
+          throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void dryRun(Object benchmark) throws InvalidBenchmarkException {}
+
+        @Override
+        MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
+          throw new UnsupportedOperationException();
+        }
+      };
+    }
+
+    @Override public TrialSchedulingPolicy schedulingPolicy() {
+      return TrialSchedulingPolicy.SERIAL;
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/FakeWorkers.java b/caliper/src/test/java/com/google/caliper/runner/FakeWorkers.java
new file mode 100644
index 0000000..309a462
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/FakeWorkers.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2014 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.runner;
+
+import com.google.caliper.bridge.LogMessage;
+import com.google.caliper.bridge.LogMessageVisitor;
+import com.google.caliper.bridge.OpenedSocket;
+import com.google.caliper.config.CaliperConfig;
+import com.google.caliper.config.InvalidConfigurationException;
+import com.google.caliper.platform.Platform;
+import com.google.caliper.platform.jvm.JvmPlatform;
+import com.google.caliper.util.Util;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * A collection of Simple java executables and a helper method for creating process builders for 
+ * them.
+ */
+final class FakeWorkers {
+
+  @GuardedBy("FakeWorkers.class")
+  private static VirtualMachine jvm;
+
+  /** 
+   * Try to find the currently executing jvm binary, N.B. This isn't guaranteed to be cross 
+   * platform.
+   */
+  private static synchronized VirtualMachine init() {
+    if (jvm == null) {
+      try {
+        Platform platform = new JvmPlatform();
+        jvm = new VirtualMachine("default", 
+            new CaliperConfig(ImmutableMap.<String, String>of()).getDefaultVmConfig(platform));
+      } catch (InvalidConfigurationException e) {
+        throw new RuntimeException();
+      }
+    }
+    return jvm;
+  }
+
+  /** 
+   * Returns a ProcessBuilder that attempts to invoke the given class as main in a JVM configured
+   * with a classpath equivalent to the currently executing JVM.
+   */
+  static ProcessBuilder createProcessBuilder(Class<?> mainClass, String ...mainArgs) {
+    VirtualMachine jvm = init();
+    List<String> args;
+    try {
+      args = WorkerProcess.getJvmArgs(jvm, BenchmarkClass.forClass(mainClass));
+    } catch (InvalidBenchmarkException e) {
+      throw new RuntimeException(e);
+    }
+    args.add(mainClass.getName());
+    Collections.addAll(args, mainArgs);
+    return new ProcessBuilder().command(args);
+  }
+
+  public static VirtualMachine getVirtualMachine() {
+    return init();
+  }
+
+  /** 
+   * A simple main method that will sleep for the number of milliseconds specified in the first
+   * argument.
+   */
+  static final class Sleeper {
+    public static void main(String[] args) throws NumberFormatException, InterruptedException {
+      Thread.sleep(Long.parseLong(args[0]));
+    }
+  }
+  
+  /** 
+   * A simple main method that exits immediately with the code provided by the first argument
+   */
+  static final class Exit {
+    public static void main(String[] args) {
+      System.exit(Integer.parseInt(args[0]));
+    }
+  }
+  
+  /** 
+   * A simple main method that exits immediately with the code provided by the first argument
+   */
+  static final class CloseAndSleep {
+    public static void main(String[] args) throws IOException, InterruptedException {
+      System.err.close();
+      System.in.close();
+      System.out.close();
+      new CountDownLatch(1).await();  // wait forever
+    }
+  }
+  
+  /** 
+   * Prints alternating arguments to standard out and standard error.
+   */
+  static final class PrintClient {
+    public static void main(String[] args)  {
+      for (int i = 0; i < args.length; i++) {
+        if (i % 2 == 0) {
+          System.out.println(args[i]);
+          System.out.flush();
+        } else {
+          System.err.println(args[i]);
+          System.err.flush();
+        }
+      }
+    }
+  }
+
+  /**
+   * Prints alternating arguments to standard out and standard error.
+   */
+  @VisibleForTesting
+  static final class LoadBenchmarkClass {
+
+    public static void main(String[] args) throws ClassNotFoundException {
+        String benchmarkClassName = args[0];
+        Util.loadClass(benchmarkClassName);
+    }
+  }
+
+  static final class DummyLogMessage extends LogMessage implements Serializable {
+    private final String content;
+
+    DummyLogMessage(String content) {
+      this.content = content;
+    }
+
+    @Override public void accept(LogMessageVisitor visitor) {}
+
+    @Override public String toString() {
+      return content;
+    }
+
+    @Override public boolean equals(Object obj) {
+      return obj instanceof DummyLogMessage && ((DummyLogMessage) obj).content.equals(content);
+    }
+
+    @Override public int hashCode() {
+      return content.hashCode();
+    }
+  }
+  
+  /** 
+   * Connects to a socket on localhost on the port provided as the first argument and echos all 
+   * data.
+   * 
+   * <p>Once the connection has been closed it prints the remaining args to stdout
+   */
+  static final class SocketEchoClient {
+    public static void main(String[] args) throws Exception {
+      int port = Integer.parseInt(args[0]);
+      OpenedSocket openedSocket = OpenedSocket.fromSocket(
+          new Socket(InetAddress.getLocalHost(), port));
+      OpenedSocket.Reader reader = openedSocket.reader();
+      OpenedSocket.Writer writer = openedSocket.writer();
+      writer.write(new DummyLogMessage("start"));
+      writer.flush();
+      Serializable obj;
+      while ((obj = reader.read()) != null) {
+        writer.write(obj);
+        writer.flush();
+      }
+      writer.close();
+      reader.close();
+      for (int i = 1; i < args.length; i++) {
+        System.out.println(args[i]);
+        System.out.flush();
+      }
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/InMemoryResultsUploader.java b/caliper/src/test/java/com/google/caliper/runner/InMemoryResultsUploader.java
new file mode 100644
index 0000000..075a6e2
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/InMemoryResultsUploader.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.caliper.api.ResultProcessor;
+import com.google.caliper.model.Trial;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * A {@link ResultProcessor} that collects all trials in a static list for easy inspection by tests.
+ */
+public class InMemoryResultsUploader implements ResultProcessor {
+  static ImmutableList<Trial> trials() {
+    return ImmutableList.copyOf(trials);
+  }
+  
+  private static List<Trial> trials;
+  private boolean isClosed;
+
+  @Inject public InMemoryResultsUploader() {
+    trials = Lists.newArrayList();
+  }
+
+  @Override public void close() throws IOException {
+    checkState(!isClosed);
+    isClosed = true;
+  }
+
+  @Override public void processTrial(Trial trial) {
+    checkState(!isClosed);
+    trials.add(trial); 
+  }
+}
\ No newline at end of file
diff --git a/caliper/src/test/java/com/google/caliper/runner/MalformedBenchmarksTest.java b/caliper/src/test/java/com/google/caliper/runner/MalformedBenchmarksTest.java
new file mode 100644
index 0000000..80a1330
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/MalformedBenchmarksTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.runner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.caliper.Benchmark;
+import com.google.caliper.Param;
+import com.google.caliper.config.InvalidConfigurationException;
+import com.google.caliper.util.InvalidCommandException;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Unit test covering common user mistakes in benchmark classes.
+ */
+@RunWith(JUnit4.class)
+
+public class MalformedBenchmarksTest {
+  // Put the expected messages together here, which may promote some kind of
+  // consistency in their wording. :)
+
+  private static final String ABSTRACT =
+      "Class '%s' is abstract";
+  private static final String NO_CONSTRUCTOR =
+      "Benchmark class %s does not have a publicly visible default constructor";
+  private static final String NO_METHODS =
+      "There were no experiments to be performed for the class %s using the instruments " +
+      "[allocation, runtime]";
+  private static final String STATIC_BENCHMARK =
+      "Benchmark methods must not be static: timeIt";
+  private static final String WRONG_ARGUMENTS =
+      "Benchmark methods must have no arguments or accept a single int or long parameter: timeIt";
+  private static final String STATIC_PARAM =
+      "Parameter field 'oops' must not be static";
+  private static final String RESERVED_PARAM =
+      "Class '%s' uses reserved parameter name 'vm'";
+  private static final String NO_CONVERSION = "Type 'Object' of parameter field 'oops' "
+      + "has no recognized String-converting method; see <TODO> for details";
+  private static final String CONVERT_FAILED = // granted this one's a little weird (and brittle)
+      "Cannot convert value 'oops' to type 'int': For input string: \"oops\"";
+
+  @Test public void abstractBenchmark() throws Exception {
+    expectException(ABSTRACT, AbstractBenchmark.class);
+  }
+  abstract static class AbstractBenchmark {}
+
+  @Test public void noSuitableConstructor() throws Exception {
+    expectException(String.format(NO_CONSTRUCTOR, BadConstructorBenchmark.class.getName()),
+        BadConstructorBenchmark.class);
+  }
+
+  @SuppressWarnings("unused")
+  static class BadConstructorBenchmark {
+    BadConstructorBenchmark(String damnParam) {}
+    @Benchmark void timeIt(int reps) {}
+  }
+
+  @Test public void noBenchmarkMethods() throws Exception {
+    expectException(NO_METHODS, NoMethodsBenchmark.class);
+  }
+
+  @SuppressWarnings("unused")
+  static class NoMethodsBenchmark {
+    void timeIt(int reps) {} // not annotated
+  }
+
+  @Test public void staticBenchmarkMethod() throws Exception {
+    expectException(STATIC_BENCHMARK, StaticBenchmarkMethodBenchmark.class);
+  }
+
+  @SuppressWarnings("unused")
+  static class StaticBenchmarkMethodBenchmark {
+    @Benchmark public static void timeIt(int reps) {}
+  }
+
+  @Test public void wrongSignature() throws Exception {
+    expectException(WRONG_ARGUMENTS, BoxedParamBenchmark.class);
+    expectException(WRONG_ARGUMENTS, ExtraParamBenchmark.class);
+  }
+
+  @SuppressWarnings("unused")
+  static class BoxedParamBenchmark {
+    @Benchmark void timeIt(Integer reps) {}
+  }
+
+  @SuppressWarnings("unused")
+  static class ExtraParamBenchmark {
+    @Benchmark void timeIt(int reps, int what) {}
+  }
+
+  @Test public void hasBenchmarkOverloads() throws Exception {
+    // N.B. baz is fine since although it has an overload, its overload is not a benchmark method.
+    expectException(
+        "Overloads are disallowed for benchmark methods, found overloads of [bar, foo] in "
+        + "benchmark OverloadsAnnotatedBenchmark",
+        OverloadsAnnotatedBenchmark.class);
+  }
+
+  @SuppressWarnings("unused")
+  static class OverloadsAnnotatedBenchmark {
+    @Benchmark public void foo(long reps) {}
+    @Benchmark public void foo(int reps) {}
+    @Benchmark public void bar(long reps) {}
+    @Benchmark public void bar(int reps) {}
+    @Benchmark public void baz(int reps) {}
+    public void baz(long reps, boolean thing) {}
+    public void baz(long reps) {}
+  }
+
+  @Test public void staticParam() throws Exception {
+    expectException(STATIC_PARAM, StaticParamBenchmark.class);
+  }
+  static class StaticParamBenchmark {
+    @Param static String oops;
+  }
+
+  @Test public void reservedParameterName() throws Exception {
+    expectException(RESERVED_PARAM, ReservedParamBenchmark.class);
+  }
+  static class ReservedParamBenchmark {
+    @Param String vm;
+  }
+
+  @Test public void unparsableParamType() throws Exception {
+    expectException(NO_CONVERSION, UnparsableParamTypeBenchmark.class);
+  }
+  static class UnparsableParamTypeBenchmark {
+    @Param Object oops;
+  }
+
+  @Test public void unparsableParamDefault() throws Exception {
+    expectException(CONVERT_FAILED, UnparsableParamDefaultBenchmark.class);
+  }
+  static class UnparsableParamDefaultBenchmark {
+    @Param({"1", "2", "oops"}) int number;
+  }
+
+  // end of tests
+
+  private void expectException(String expectedMessageFmt, Class<?> benchmarkClass)
+      throws InvalidCommandException, InvalidConfigurationException {
+    try {
+      CaliperMain.exitlessMain(
+          new String[] {"--instrument=allocation,runtime", "--dry-run", benchmarkClass.getName()},
+          new PrintWriter(new StringWriter()), new PrintWriter(new StringWriter()));
+      fail("no exception thrown");
+    } catch (InvalidBenchmarkException e) {
+      try {
+        String expectedMessageText =
+            String.format(expectedMessageFmt, benchmarkClass.getSimpleName());
+        assertEquals(expectedMessageText, e.getMessage());
+
+        // don't swallow our real stack trace
+      } catch (AssertionFailedError afe) {
+        afe.initCause(e);
+        throw afe;
+      }
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/ResultProcessorCreatorTest.java b/caliper/src/test/java/com/google/caliper/runner/ResultProcessorCreatorTest.java
new file mode 100644
index 0000000..d88ee39
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/ResultProcessorCreatorTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015 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.runner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.caliper.api.ResultProcessor;
+import com.google.caliper.model.Trial;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import java.io.IOException;
+
+/**
+ * Unit test to ensure that {@link ResultProcessorCreator} works properly.
+ */
+@RunWith(JUnit4.class)
+public class ResultProcessorCreatorTest {
+
+  public static final String NOT_SUPPORTED =
+      "ResultProcessor %s not supported as it does not have a public default constructor";
+
+  @Test
+  public void testNotPublicConstructor() {
+    try {
+      ResultProcessorCreator.createResultProcessor(NoPublicConstructorResultProcessor.class);
+      fail("Did not fail on non-public constructor");
+    } catch (UserCodeException e) {
+      assertEquals(String.format(NOT_SUPPORTED, NoPublicConstructorResultProcessor.class),
+          e.getMessage());
+    }
+  }
+
+  public static class NoPublicConstructorResultProcessor implements ResultProcessor {
+
+    NoPublicConstructorResultProcessor() {
+    }
+
+    @Override
+    public void processTrial(Trial trial) {
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+  }
+
+  @Test
+  public void testPublicButNotDefaultConstructor() {
+    try {
+      ResultProcessorCreator.createResultProcessor(
+          PublicButNotDefaultDefaultConstructorResultProcessor.class);
+      fail("Did not fail on public but not default constructor");
+    } catch (UserCodeException e) {
+      assertEquals(
+          String.format(NOT_SUPPORTED, PublicButNotDefaultDefaultConstructorResultProcessor.class),
+          e.getMessage());
+    }
+  }
+
+  public static class PublicButNotDefaultDefaultConstructorResultProcessor
+      implements ResultProcessor {
+
+    public PublicButNotDefaultDefaultConstructorResultProcessor(
+        @SuppressWarnings("UnusedParameters") int i) {
+    }
+
+    @Override
+    public void processTrial(Trial trial) {
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+  }
+
+  @Test
+  public void testPublicConstructor() {
+    ResultProcessor processor =
+        ResultProcessorCreator.createResultProcessor(PublicDefaultConstructorResultProcessor.class);
+    assertTrue(processor instanceof PublicDefaultConstructorResultProcessor);
+  }
+
+  public static class PublicDefaultConstructorResultProcessor implements ResultProcessor {
+
+    @Override
+    public void processTrial(Trial trial) {
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/RuntimeInstrumentTest.java b/caliper/src/test/java/com/google/caliper/runner/RuntimeInstrumentTest.java
new file mode 100644
index 0000000..e960c03
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/RuntimeInstrumentTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.caliper.Benchmark;
+import com.google.caliper.api.BeforeRep;
+import com.google.caliper.api.Macrobenchmark;
+import com.google.caliper.runner.Instrument.Instrumentation;
+import com.google.caliper.util.ShortDuration;
+import com.google.caliper.worker.MacrobenchmarkWorker;
+import com.google.caliper.worker.RuntimeWorker;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.Uninterruptibles;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link RuntimeInstrument}.
+ */
+@RunWith(JUnit4.class)
+public class RuntimeInstrumentTest {
+  @Rule public CaliperTestWatcher runner = new CaliperTestWatcher();
+
+  private RuntimeInstrument instrument;
+
+  @Before public void createInstrument() {
+    this.instrument = new RuntimeInstrument(ShortDuration.of(100, NANOSECONDS));
+  }
+
+  @Test public void isBenchmarkMethod() {
+    assertEquals(
+        ImmutableSet.of("macrobenchmark", "microbenchmark", "picobenchmark", "integerParam"),
+        FluentIterable.from(Arrays.asList(RuntimeBenchmark.class.getDeclaredMethods()))
+            .filter(new Predicate<Method>() {
+              @Override public boolean apply(Method input) {
+                return instrument.isBenchmarkMethod(input);
+              }
+            })
+            .transform(new Function<Method, String>() {
+              @Override public String apply(Method input) {
+                return input.getName();
+              }
+            })
+            .toSet());
+  }
+
+  @Test public void createInstrumentation_macrobenchmark() throws Exception {
+    Method benchmarkMethod = RuntimeBenchmark.class.getDeclaredMethod("macrobenchmark");
+    Instrumentation instrumentation = instrument.createInstrumentation(benchmarkMethod);
+    assertEquals(benchmarkMethod, instrumentation.benchmarkMethod());
+    assertEquals(instrument, instrumentation.instrument());
+    assertEquals(MacrobenchmarkWorker.class, instrumentation.workerClass());
+  }
+
+  @Test public void createInstrumentation_microbenchmark() throws Exception {
+    Method benchmarkMethod = RuntimeBenchmark.class.getDeclaredMethod("microbenchmark", int.class);
+    Instrumentation instrumentation = instrument.createInstrumentation(benchmarkMethod);
+    assertEquals(benchmarkMethod, instrumentation.benchmarkMethod());
+    assertEquals(instrument, instrumentation.instrument());
+    assertEquals(RuntimeWorker.Micro.class, instrumentation.workerClass());
+  }
+
+  @Test public void createInstrumentation_picobenchmark() throws Exception {
+    Method benchmarkMethod = RuntimeBenchmark.class.getDeclaredMethod("picobenchmark", long.class);
+    Instrumentation instrumentation = instrument.createInstrumentation(benchmarkMethod);
+    assertEquals(benchmarkMethod, instrumentation.benchmarkMethod());
+    assertEquals(instrument, instrumentation.instrument());
+    assertEquals(RuntimeWorker.Pico.class, instrumentation.workerClass());
+  }
+
+  @Test public void createInstrumentation_badParam() throws Exception {
+    Method benchmarkMethod =
+        RuntimeBenchmark.class.getDeclaredMethod("integerParam", Integer.class);
+    try {
+      instrument.createInstrumentation(benchmarkMethod);
+      fail();
+    } catch (InvalidBenchmarkException expected) {}
+  }
+
+  @Test public void createInstrumentation_notAMacrobenchmark() throws Exception {
+    Method benchmarkMethod = RuntimeBenchmark.class.getDeclaredMethod("notAMacrobenchmark");
+    try {
+      instrument.createInstrumentation(benchmarkMethod);
+      fail();
+    } catch (IllegalArgumentException expected) {}
+  }
+
+  @Test public void createInstrumentationnotAMicrobenchmark() throws Exception {
+    Method benchmarkMethod =
+        RuntimeBenchmark.class.getDeclaredMethod("notAMicrobenchmark", int.class);
+    try {
+      instrument.createInstrumentation(benchmarkMethod);
+      fail();
+    } catch (IllegalArgumentException expected) {}
+  }
+
+  @Test public void createInstrumentation_notAPicobenchmark() throws Exception {
+    Method benchmarkMethod =
+        RuntimeBenchmark.class.getDeclaredMethod("notAPicobenchmark", long.class);
+    try {
+      instrument.createInstrumentation(benchmarkMethod);
+      fail();
+    } catch (IllegalArgumentException expected) {}
+  }
+
+  @SuppressWarnings("unused")
+  private static final class RuntimeBenchmark {
+    @Benchmark void macrobenchmark() {}
+    @Benchmark void microbenchmark(int reps) {}
+    @Benchmark void picobenchmark(long reps) {}
+
+    @Benchmark void integerParam(Integer oops) {}
+
+    void notAMacrobenchmark() {}
+    void notAMicrobenchmark(int reps) {}
+    void notAPicobenchmark(long reps) {}
+  }
+
+  private double relativeDifference(double a, double b) {
+    return Math.abs(a - b) / ((a + b) / 2.0);
+  }
+
+  static final class TestBenchmark {
+    @Benchmark long pico(long reps) {
+      long dummy = 0;
+      for (long i = 0; i < reps; i++) {
+        dummy += spin();
+      }
+      return dummy;
+    }
+
+    @Benchmark long micro(int reps) {
+      long dummy = 0;
+      for (int i = 0; i < reps; i++) {
+        dummy += spin();
+      }
+      return dummy;
+    }
+
+    @Macrobenchmark long macro() {
+      return spin();
+    }
+  }
+
+  // busy spin for 10ms and return the elapsed time.  N.B. we busy spin instead of sleeping so
+  // that we aren't put at the mercy (and variance) of the thread scheduler.
+  private static long spin() {
+    long remainingNanos = TimeUnit.MILLISECONDS.toNanos(10);
+    long start = System.nanoTime();
+    long elapsed;
+    while ((elapsed = System.nanoTime() - start) < remainingNanos) {}
+    return elapsed;
+  }
+
+  @Test
+
+  public void gcBeforeEachOptionIsHonored() throws Exception {
+    runBenchmarkWithKnownHeap(true);
+    // The GC error will only be avoided if gcBeforeEach is true, and
+    // honored by the MacrobenchmarkWorker.
+    assertFalse("No GC warning should be printed to stderr",
+        runner.getStdout().toString().contains("WARNING: GC occurred during timing."));
+  }
+
+  @Test
+
+  public void gcBeforeEachOptionIsReallyNecessary() throws Exception {
+    // Verifies that we indeed get a GC warning if gcBeforeEach = false.
+    runBenchmarkWithKnownHeap(false);
+    assertTrue("A GC warning should be printed to stderr if gcBeforeEach isn't honored",
+        runner.getStdout().toString().contains("WARNING: GC occurred during timing."));
+  }
+
+  private void runBenchmarkWithKnownHeap(boolean gcBeforeEach) throws Exception {
+    runner.forBenchmark(BenchmarkThatAllocatesALot.class)
+        .instrument("runtime")
+        .options(
+            "-Cvm.args=-Xmx512m",
+            "-Cinstrument.runtime.options.measurements=10",
+            "-Cinstrument.runtime.options.gcBeforeEach=" + gcBeforeEach,
+            "--time-limit=30s")
+        .run();
+  }
+
+  static final class BenchmarkThatAllocatesALot {
+    @Benchmark
+    int benchmarkMethod() {
+      // Any larger and the GC doesn't manage to make enough space, resulting in
+      // OOMErrors in both test cases above.
+      long[] array = new long[32 * 1024 * 1024];
+      return array.length;
+    }
+  }
+
+  @Test
+
+  public void maxWarmupWallTimeOptionIsHonored() throws Exception {
+    runner.forBenchmark(MacroBenchmarkWithLongBeforeRep.class)
+        .instrument("runtime")
+        .options(
+            "-Cinstrument.runtime.options.maxWarmupWallTime=100ms",
+            "--time-limit=10s")
+        .run();
+
+    assertTrue(
+        "The maxWarmupWallTime should trigger an interruption of warmup and a warning "
+            + "should be printed to stderr",
+        runner.getStdout().toString().contains(
+            "WARNING: Warmup was interrupted "
+                + "because it took longer than 100ms of wall-clock time."));
+  }
+
+  static final class MacroBenchmarkWithLongBeforeRep {
+    @BeforeRep
+    public void beforeRepMuchLongerThanBenchmark() {
+      Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
+    }
+
+    @Benchmark
+    long prettyFastMacroBenchmark() {
+      return spin();
+    }
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/ServerSocketServiceTest.java b/caliper/src/test/java/com/google/caliper/runner/ServerSocketServiceTest.java
new file mode 100644
index 0000000..8add40b
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/ServerSocketServiceTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.caliper.bridge.OpenedSocket;
+import com.google.caliper.bridge.StartupAnnounceMessage;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Tests for {@link ServerSocketService}.
+ */
+@RunWith(JUnit4.class)
+
+public class ServerSocketServiceTest {
+
+  private final ServerSocketService service = new ServerSocketService();
+  private int port;
+
+  @Before public void startService() {
+    service.startAsync().awaitRunning();
+    port = service.getPort();
+  }
+
+  @After public void stopService() {
+    service.stopAsync().awaitTerminated();
+  }
+
+  @Test public void getConnectionId_requestComesInFirst() throws Exception {
+    UUID id = UUID.randomUUID();
+    ListenableFuture<OpenedSocket> pendingServerConnection = service.getConnection(id);
+    assertFalse(pendingServerConnection.isDone());
+    OpenedSocket clientSocket = openConnectionAndIdentify(id);
+    // Assert that the ends are hooked up to each other
+    assertEndsConnected(clientSocket, pendingServerConnection.get());
+  }
+
+  @Test public void getConnectionIdTwice_acceptComesFirst() throws Exception {
+    UUID id = UUID.randomUUID();
+    OpenedSocket clientSocket = openConnectionAndIdentify(id);
+
+    ListenableFuture<OpenedSocket> pendingServerConnection = service.getConnection(id);
+    // wait for the service to fully initialize the connection
+    OpenedSocket serverSocket = pendingServerConnection.get();
+    assertEndsConnected(clientSocket, serverSocket);
+    try {
+      // the second request is an error
+      service.getConnection(id).get();
+      fail();
+    } catch (IllegalStateException expected) {}
+  }
+
+  @Test public void getConnectionStoppedService() throws Exception {
+    UUID id = UUID.randomUUID();
+    ListenableFuture<OpenedSocket> pendingServerConnection = service.getConnection(id);
+    assertFalse(pendingServerConnection.isDone());
+    service.stopAsync().awaitTerminated();
+    assertTrue(pendingServerConnection.isDone());
+
+    try {
+      pendingServerConnection.get();
+      fail();
+    } catch (ExecutionException e) {
+      assertEquals("The socket has been closed", e.getCause().getMessage());
+    }
+
+    try {
+      service.getConnection(UUID.randomUUID());
+      fail();
+    } catch (IllegalStateException expected) {}
+  }
+
+  private OpenedSocket openClientConnection() throws IOException {
+    return OpenedSocket.fromSocket(new Socket(InetAddress.getLoopbackAddress(), port));
+  }
+
+  /**
+   * Opens a connection to the service and identifies itself using the id.
+   */
+  private OpenedSocket openConnectionAndIdentify(UUID id) throws IOException {
+    OpenedSocket clientSocket = openClientConnection();
+    OpenedSocket.Writer writer = clientSocket.writer();
+    writer.write(new StartupAnnounceMessage(id));
+    writer.flush();
+    return clientSocket;
+  }
+
+  private void assertEndsConnected(OpenedSocket clientSocket, OpenedSocket serverSocket)
+      throws IOException {
+    serverSocket.writer().write("hello client!");
+    serverSocket.writer().flush();  // necessary to prevent deadlock
+    assertEquals("hello client!", clientSocket.reader().read());
+
+    clientSocket.writer().write("hello server!");
+    clientSocket.writer().flush();  // ditto
+    assertEquals("hello server!", serverSocket.reader().read());
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/StreamServiceTest.java b/caliper/src/test/java/com/google/caliper/runner/StreamServiceTest.java
new file mode 100644
index 0000000..f20e72a
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/StreamServiceTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2013 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.runner;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.caliper.bridge.LogMessage;
+import com.google.caliper.bridge.OpenedSocket;
+import com.google.caliper.runner.FakeWorkers.DummyLogMessage;
+import com.google.caliper.runner.StreamService.StreamItem;
+import com.google.caliper.runner.StreamService.StreamItem.Kind;
+import com.google.caliper.util.Parser;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListenableFutureTask;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.Service.Listener;
+import com.google.common.util.concurrent.Service.State;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.ServerSocket;
+import java.net.SocketException;
+import java.text.ParseException;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link StreamService}.
+ */
+@RunWith(JUnit4.class)
+
+public class StreamServiceTest {
+
+  private ServerSocket serverSocket;
+  private final StringWriter writer = new StringWriter();
+  private final PrintWriter stdout = new PrintWriter(writer, true);
+  private final Parser<LogMessage> parser = new Parser<LogMessage>() {
+    @Override public LogMessage parse(final CharSequence text) throws ParseException {
+      return new DummyLogMessage(text.toString());
+    }
+  };
+
+  private StreamService service;
+  private final CountDownLatch terminalLatch = new CountDownLatch(1);
+  private static final int TRIAL_NUMBER = 3;
+
+  @Before public void setUp() throws IOException {
+    serverSocket = new ServerSocket(0);
+  }
+
+  @After public void closeSocket() throws IOException {
+    serverSocket.close();
+  }
+
+  @After public void stopService() {
+    if (service != null && service.state() != State.FAILED && service.state() != State.TERMINATED) {
+      service.stopAsync().awaitTerminated();
+    }
+  }
+
+  @Test public void testReadOutput() throws Exception {
+    makeService(FakeWorkers.PrintClient.class, "foo", "bar");
+    service.startAsync().awaitRunning();
+    StreamItem item1 = readItem();
+    assertEquals(Kind.DATA, item1.kind());
+    Set<String> lines = Sets.newHashSet();
+    lines.add(item1.content().toString());
+    StreamItem item2 = readItem();
+    assertEquals(Kind.DATA, item2.kind());
+    lines.add(item2.content().toString());
+    assertEquals(Sets.newHashSet("foo", "bar"), lines);
+    assertEquals(State.RUNNING, service.state());
+    StreamItem item3 = readItem();
+    assertEquals(Kind.EOF, item3.kind());
+    awaitStopped(100, TimeUnit.MILLISECONDS);
+    assertTerminated();
+  }
+
+  @Test public void failingProcess() throws Exception {
+    makeService(FakeWorkers.Exit.class, "1");
+    service.startAsync().awaitRunning();
+    assertEquals(Kind.EOF, readItem().kind());
+    awaitStopped(100, TimeUnit.MILLISECONDS);
+    assertEquals(State.FAILED, service.state());
+  }
+
+  @Test public void processDoesntExit() throws Exception {
+    // close all fds and then sleep
+    makeService(FakeWorkers.CloseAndSleep.class);
+    service.startAsync().awaitRunning();
+    assertEquals(Kind.EOF, readItem().kind());
+    awaitStopped(200, TimeUnit.MILLISECONDS);  // we
+    assertEquals(State.FAILED, service.state());
+  }
+
+  @Test public void testSocketInputOutput() throws Exception {
+    int localport = serverSocket.getLocalPort();
+    // read from the socket and echo it back
+    makeService(FakeWorkers.SocketEchoClient.class, Integer.toString(localport));
+
+    service.startAsync().awaitRunning();
+    assertEquals(new DummyLogMessage("start"), readItem().content());
+    service.sendMessage(new DummyLogMessage("hello socket world"));
+    assertEquals(new DummyLogMessage("hello socket world"), readItem().content());
+    service.closeWriter();
+    assertEquals(State.RUNNING, service.state());
+    StreamItem nextItem = readItem();
+    assertEquals("Expected EOF " + nextItem, Kind.EOF, nextItem.kind());
+    awaitStopped(100, TimeUnit.MILLISECONDS);
+    assertTerminated();
+  }
+
+  @Test public void testSocketClosesBeforeProcess() throws Exception {
+    int localport = serverSocket.getLocalPort();
+    // read from the socket and echo it back
+    makeService(FakeWorkers.SocketEchoClient.class, Integer.toString(localport), "foo");
+    service.startAsync().awaitRunning();
+    assertEquals(new DummyLogMessage("start"), readItem().content());
+    service.sendMessage(new DummyLogMessage("hello socket world"));
+    assertEquals(new DummyLogMessage("hello socket world"), readItem().content());
+    service.closeWriter();
+
+    assertEquals("foo", readItem().content().toString());
+
+    assertEquals(State.RUNNING, service.state());
+    assertEquals(Kind.EOF, readItem().kind());
+    awaitStopped(100, TimeUnit.MILLISECONDS);
+    assertTerminated();
+  }
+
+  @Test public void failsToAcceptConnection() throws Exception {
+    serverSocket.close();  // This will force serverSocket.accept to throw a SocketException
+    makeService(FakeWorkers.Sleeper.class, Long.toString(TimeUnit.MINUTES.toMillis(10)));
+    try {
+      service.startAsync().awaitRunning();
+      fail();
+    } catch (IllegalStateException expected) {}
+    assertEquals(SocketException.class, service.failureCause().getClass());
+  }
+
+  /** Reads an item, asserting that there was no timeout. */
+  private StreamItem readItem() throws InterruptedException {
+    StreamItem item = service.readItem(10, TimeUnit.SECONDS);
+    assertNotSame("Timed out while reading item from worker", Kind.TIMEOUT, item.kind());
+    return item;
+  }
+
+  /**
+   * Wait for the service to reach a terminal state without calling stop.
+   */
+  private void awaitStopped(long time, TimeUnit unit) throws InterruptedException {
+    assertTrue(terminalLatch.await(time, unit));
+  }
+
+  private void assertTerminated() {
+    State state = service.state();
+    if (state != State.TERMINATED) {
+      if (state == State.FAILED) {
+        throw new AssertionError(service.failureCause());
+      }
+      fail("Expected service to be terminated but was: " + state);
+    }
+  }
+
+  @SuppressWarnings("resource")
+  private void makeService(Class<?> main, String ...args) {
+    checkState(service == null, "You can only make one StreamService per test");
+    UUID trialId = UUID.randomUUID();
+    TrialOutputLogger trialOutput = new TrialOutputLogger(new TrialOutputFactory() {
+      @Override public FileAndWriter getTrialOutputFile(int trialNumber)
+          throws FileNotFoundException {
+        checkArgument(trialNumber == TRIAL_NUMBER);
+        return new FileAndWriter(new File("/tmp/not-a-file"), stdout);
+      }
+
+      @Override public void persistFile(File f) {
+        throw new UnsupportedOperationException();
+      }
+
+    }, TRIAL_NUMBER, trialId, null /* experiment */);
+    try {
+      // normally the TrialRunLoop opens/closes the logger
+      trialOutput.open();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    service = new StreamService(
+        new WorkerProcess(FakeWorkers.createProcessBuilder(main, args),
+            trialId,
+            getSocketFuture(),
+            new RuntimeShutdownHookRegistrar()),
+        parser,
+        trialOutput);
+    service.addListener(new Listener() {
+      @Override public void starting() {}
+      @Override public void running() {}
+      @Override public void stopping(State from) {}
+      @Override public void terminated(State from) {
+        terminalLatch.countDown();
+      }
+      @Override public void failed(State from, Throwable failure) {
+        terminalLatch.countDown();
+      }
+    }, MoreExecutors.directExecutor());
+  }
+
+  private ListenableFuture<OpenedSocket> getSocketFuture() {
+    ListenableFutureTask<OpenedSocket> openSocketTask = ListenableFutureTask.create(
+        new Callable<OpenedSocket>() {
+          @Override
+          public OpenedSocket call() throws Exception {
+            return OpenedSocket.fromSocket(serverSocket.accept());
+          }
+        });
+    // N.B. this thread will block on serverSocket.accept until a connection is accepted or the
+    // socket is closed, so no matter what this thread will die with the test.
+    Thread opener = new Thread(openSocketTask, "SocketOpener");
+    opener.setDaemon(true);
+    opener.start();
+    return openSocketTask;
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/runner/WorkerProcessTest.java b/caliper/src/test/java/com/google/caliper/runner/WorkerProcessTest.java
new file mode 100644
index 0000000..397f7d8
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/runner/WorkerProcessTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2012 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.runner;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.caliper.Benchmark;
+import com.google.caliper.config.VmConfig;
+import com.google.caliper.model.BenchmarkSpec;
+import com.google.caliper.platform.jvm.JvmPlatform;
+import com.google.caliper.worker.WorkerMain;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Tests {@link WorkerProcess}.
+ *
+ * <p>TODO(lukes,gak): write more tests for how our specs get turned into commandlines
+ */
+
+@RunWith(JUnit4.class)
+public class WorkerProcessTest {
+  private static final int PORT_NUMBER = 4004;
+  private static final UUID TRIAL_ID = UUID.randomUUID();
+
+  private static class MockRegistrar implements ShutdownHookRegistrar {
+    Set<Thread> hooks = Sets.newHashSet();
+    @Override public void addShutdownHook(Thread hook) {
+      hooks.add(hook);
+    }
+    @Override public boolean removeShutdownHook(Thread hook) {
+      return hooks.remove(hook);
+    }
+  }
+
+  private final MockRegistrar registrar = new MockRegistrar();
+  private BenchmarkClass benchmarkClass;
+
+  @Before public void setUp() throws InvalidBenchmarkException {
+    benchmarkClass = BenchmarkClass.forClass(TestBenchmark.class);
+  }
+
+  @Test public void simpleArgsTest() throws Exception {
+    Method method = TestBenchmark.class.getDeclaredMethods()[0];
+    AllocationInstrument allocationInstrument = new AllocationInstrument();
+    allocationInstrument.setOptions(ImmutableMap.of("trackAllocations", "true"));
+    VmConfig vmConfig = new VmConfig(
+        new File("foo"),
+        Arrays.asList("--doTheHustle"),
+        new File("java"),
+        new JvmPlatform());
+    Experiment experiment = new Experiment(
+        allocationInstrument.createInstrumentation(method),
+        ImmutableMap.<String, String>of(),
+        new VirtualMachine("foo-jvm", vmConfig));
+    BenchmarkSpec spec = new BenchmarkSpec.Builder()
+        .className(TestBenchmark.class.getName())
+        .methodName(method.getName())
+        .build();
+    ProcessBuilder builder = createProcess(experiment, spec);
+    List<String> commandLine = builder.command();
+    assertEquals(new File("java").getAbsolutePath(), commandLine.get(0));
+    assertEquals("--doTheHustle", commandLine.get(1));  // vm specific flags come next
+    assertEquals("-cp", commandLine.get(2));  // then the classpath
+    // should we assert on classpath contents?
+    ImmutableSet<String> extraCommandLineArgs =
+        allocationInstrument.getExtraCommandLineArgs(vmConfig);
+    assertEquals(extraCommandLineArgs.asList(),
+        commandLine.subList(4, 4 + extraCommandLineArgs.size()));
+    int index = 4 + extraCommandLineArgs.size();
+    assertEquals("-XX:+PrintFlagsFinal", commandLine.get(index));
+    assertEquals("-XX:+PrintCompilation", commandLine.get(++index));
+    assertEquals("-XX:+PrintGC", commandLine.get(++index));
+    assertEquals(WorkerMain.class.getName(), commandLine.get(++index));
+    // followed by worker args...
+  }
+
+  @Test public void shutdownHook_waitFor() throws Exception {
+    Process worker = createWorkerProcess(FakeWorkers.Exit.class, "0").startWorker();
+    assertEquals("worker-shutdown-hook-" + TRIAL_ID,
+        Iterables.getOnlyElement(registrar.hooks).getName());
+    worker.waitFor();
+    assertTrue(registrar.hooks.isEmpty());
+  }
+
+  @Test public void shutdownHook_exitValueThrows() throws Exception {
+    Process worker = createWorkerProcess(
+        FakeWorkers.Sleeper.class, Long.toString(MINUTES.toMillis(1))).startWorker();
+    try {
+      Thread hook = Iterables.getOnlyElement(registrar.hooks);
+      assertEquals("worker-shutdown-hook-" + TRIAL_ID, hook.getName());
+      try {
+        worker.exitValue();
+        fail();
+      } catch (IllegalThreadStateException expected) {}
+      assertTrue(registrar.hooks.contains(hook));
+    } finally {
+      worker.destroy(); // clean up
+    }
+  }
+
+  @Test public void shutdownHook_exitValue() throws Exception {
+    Process worker = createWorkerProcess(FakeWorkers.Exit.class, "0").startWorker();
+    while (true) {
+      try {
+        worker.exitValue();
+        assertTrue(registrar.hooks.isEmpty());
+        break;
+      } catch (IllegalThreadStateException e) {
+        Thread.sleep(10);  // keep polling
+      }
+    }
+  }
+
+  @Test public void shutdownHook_destroy() throws Exception {
+    Process worker = createWorkerProcess(
+        FakeWorkers.Sleeper.class, Long.toString(MINUTES.toMillis(1))).startWorker();
+    worker.destroy();
+    assertTrue(registrar.hooks.isEmpty());
+  }
+
+  static final class TestBenchmark {
+    @Benchmark long thing(long reps) {
+      long dummy = 0;
+      for (long i = 0; i < reps; i++) {
+        dummy += new Long(dummy).hashCode();
+      }
+      return dummy;
+    }
+  }
+
+  private ProcessBuilder createProcess(Experiment experiment, BenchmarkSpec benchmarkSpec) {
+    return WorkerProcess.buildProcess(TRIAL_ID, experiment, benchmarkSpec, PORT_NUMBER,
+        benchmarkClass);
+  }
+
+  private WorkerProcess createWorkerProcess(Class<?> main, String ...args) {
+    return new WorkerProcess(FakeWorkers.createProcessBuilder(main, args),
+        TRIAL_ID,
+        null,
+        registrar);
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/util/LinearTranslationTest.java b/caliper/src/test/java/com/google/caliper/util/LinearTranslationTest.java
new file mode 100644
index 0000000..4ae875b
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/util/LinearTranslationTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class LinearTranslationTest {
+  private static final double CLOSE_ENOUGH = 1.0E-13;
+
+  @Test public void linearTranslation() {
+    LinearTranslation ctof = new LinearTranslation(0, 32, 100, 212);
+    assertEquals(32, ctof.translate(0), CLOSE_ENOUGH);
+    assertEquals(212, ctof.translate(100), CLOSE_ENOUGH);
+    assertEquals(98.6, ctof.translate(37), CLOSE_ENOUGH);
+    assertEquals(-40, ctof.translate(-40), CLOSE_ENOUGH);
+
+    LinearTranslation reversed = new LinearTranslation(5, 42, 69, 0);
+    assertEquals(-21, reversed.translate(101), CLOSE_ENOUGH);
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/util/ShortDurationTest.java b/caliper/src/test/java/com/google/caliper/util/ShortDurationTest.java
new file mode 100644
index 0000000..0c32fc8
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/util/ShortDurationTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class ShortDurationTest {
+  @Test public void valueOf() {
+    assertEquals(ShortDuration.zero(), ShortDuration.valueOf("0"));
+    testIt(0, "0ns", "0s");
+    testIt(0, "0 ns", "0s");
+    testIt(0, "0nanos", "0s");
+    testIt(0, "0nanoseconds", "0s");
+    testIt(0, "0 ms", "0s");
+    testIt(0, "0us", "0s");
+    testIt(0, "1e-12 ms", "0s");
+    testIt(1, "0.501 ns", "0.501ns");
+    testIt(1000, "1 \u03bcs", "1\u03bcs");
+    // testIt(500, "0.5us", "500ns");
+    // testIt(499, "0.499000000000000000000000000000001us", "499ns");
+    // testIt(500, "0.49995 us", "500ns");
+    // testIt(60480000000000L, "0.7 days", "16.80h");
+    // testIt(Long.MAX_VALUE, "106751.99116730064591 days", "106751.99d");
+  }
+
+  @Test public void tooLongForALong() {
+    try {
+      ShortDuration.valueOf("106751.99116730064592 days");
+      fail();
+    } catch (ArithmeticException expected) {
+    }
+  }
+
+  private static void testIt(long i, String s, String p) {
+    ShortDuration d = ShortDuration.valueOf(s);
+    assertEquals(i, d.to(TimeUnit.NANOSECONDS));
+    assertEquals(p, d.toString());
+  }
+}
diff --git a/caliper/src/test/java/com/google/caliper/worker/RuntimeWorkerTest.java b/caliper/src/test/java/com/google/caliper/worker/RuntimeWorkerTest.java
new file mode 100644
index 0000000..0d75d3b
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/worker/RuntimeWorkerTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 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.worker;
+
+import static com.google.caliper.worker.RuntimeWorker.INITIAL_REPS;
+import static com.google.caliper.worker.RuntimeWorker.calculateTargetReps;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+
+import com.google.caliper.util.ShortDuration;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.math.BigDecimal;
+
+/**
+ * Tests {@link RuntimeWorker}.
+ */
+@RunWith(JUnit4.class)
+public class RuntimeWorkerTest {
+  private static final ShortDuration TIMING_INTERVAL = ShortDuration.of(100, MILLISECONDS);
+
+  @Test public void testCalculateTargetReps_tinyBenchmark() {
+    // this is one cycle on a 5GHz machine
+    ShortDuration oneCycle = ShortDuration.of(new BigDecimal("2.0e-10"), SECONDS);
+    long targetReps = calculateTargetReps(INITIAL_REPS,
+        oneCycle.times(INITIAL_REPS).to(NANOSECONDS), TIMING_INTERVAL.to(NANOSECONDS), 0.0);
+    long expectedReps = TIMING_INTERVAL.toPicos() / oneCycle.toPicos();
+    assertEquals(expectedReps, targetReps);
+  }
+
+  @Test public void testCalculateTargetReps_hugeBenchmark() {
+    long targetReps =
+        calculateTargetReps(INITIAL_REPS, HOURS.toNanos(1), TIMING_INTERVAL.to(NANOSECONDS), 0.0);
+    assertEquals(1, targetReps);
+  }
+
+  @Test public void testCalculateTargetReps_applyRandomness() {
+    long targetReps = calculateTargetReps(INITIAL_REPS, MILLISECONDS.toNanos(100),
+        TIMING_INTERVAL.to(NANOSECONDS), 0.5);
+    assertEquals(110, targetReps);
+  }
+}
diff --git a/caliper/src/test/resources/com/google/caliper/bridge/jdk6-compilation.txt b/caliper/src/test/resources/com/google/caliper/bridge/jdk6-compilation.txt
new file mode 100644
index 0000000..2227fb1
--- /dev/null
+++ b/caliper/src/test/resources/com/google/caliper/bridge/jdk6-compilation.txt
@@ -0,0 +1,288 @@
+  1       java.lang.String::hashCode (60 bytes)
+  2       java.lang.String::lastIndexOf (156 bytes)
+  3       java.lang.String::indexOf (151 bytes)
+  4       sun.nio.cs.UTF_8$Decoder::decodeArrayLoop (553 bytes)
+  5       java.io.UnixFileSystem::normalize (75 bytes)
+  6       sun.nio.cs.UTF_8$Encoder::encodeArrayLoop (490 bytes)
+  7       java.lang.String::indexOf (166 bytes)
+  1%      sun.net.www.ParseUtil::encodePath @ 29 (336 bytes)
+  8       java.lang.String::equals (88 bytes)
+  9       sun.net.www.ParseUtil::encodePath (336 bytes)
+ 10       java.lang.String::charAt (33 bytes)
+ 11       java.util.Arrays::binarySearch0 (72 bytes)
+ 12       com.google.common.base.CharMatcher$10::matches (17 bytes)
+ 13       java.util.Arrays::binarySearch (9 bytes)
+  2%      com.google.common.base.CharMatcher::setBits @ 3 (28 bytes)
+ 14       com.google.common.base.CharMatcher::setBits (28 bytes)
+ 15       java.lang.Object::<init> (1 bytes)
+ 16       java.util.zip.ZipFile::ensureOpen (37 bytes)
+ 17       java.util.zip.ZipFile::access$300 (5 bytes)
+ 18       java.util.zip.ZipFile::access$100 (5 bytes)
+ 19       java.util.HashMap::indexFor (6 bytes)
+ 20       java.util.HashMap::hash (23 bytes)
+ 21       java.lang.String::<init> (20 bytes)
+ 22       java.lang.String::substring (83 bytes)
+ 23       java.util.HashMap::get (79 bytes)
+ 24       java.lang.String::lastIndexOf (12 bytes)
+---   n   java.util.zip.ZipFile::freeEntry (static)
+---   n   java.util.zip.ZipEntry::initFields
+ 25       java.util.zip.ZipFile::access$800 (6 bytes)
+ 26       java.util.jar.JarFile$JarFileEntry::<init> (11 bytes)
+ 27       java.util.jar.JarEntry::<init> (6 bytes)
+---   n   java.util.zip.ZipFile::getNextEntry (static)
+ 28       java.util.zip.ZipEntry::<init> (102 bytes)
+ 29       java.util.jar.JarFile$1::hasMoreElements (10 bytes)
+ 30  !    java.util.zip.ZipFile$2::hasMoreElements (41 bytes)
+ 31       sun.misc.URLClassPath::getResourceMapKey (55 bytes)
+ 32       java.util.jar.JarFile$1::nextElement (5 bytes)
+ 33       java.util.jar.JarFile$1::nextElement (26 bytes)
+ 34       java.util.zip.ZipFile$2::nextElement (5 bytes)
+ 35  !    java.util.zip.ZipFile$2::nextElement (211 bytes)
+ 36       java.util.zip.ZipFile::access$400 (6 bytes)
+ 37       java.util.zip.ZipEntry::<init> (43 bytes)
+ 38       sun.misc.URLClassPath$JarLoader::addJarEntriesToEntryMap (202 bytes)
+ 39       java.util.ArrayList::get (11 bytes)
+ 40       java.util.ArrayList::rangeCheck (22 bytes)
+ 41       java.util.ArrayList::elementData (7 bytes)
+ 23   made not entrant  (2)  java.util.HashMap::get (79 bytes)
+ 42       java.lang.String::replace (142 bytes)
+ 43       java.util.Properties$LineReader::readLine (452 bytes)
+ 44       java.lang.String::startsWith (78 bytes)
+---   n   java.lang.System::arraycopy (static)
+ 45       java.util.HashMap::get (79 bytes)
+ 46       java.io.DataOutputStream::writeUTF (435 bytes)
+ 47  !    sun.reflect.generics.parser.SignatureParser::current (40 bytes)
+ 48       java.io.DataOutputStream::incCount (20 bytes)
+ 49 s     java.io.ByteArrayOutputStream::write (55 bytes)
+ 50       java.lang.CharacterData::of (120 bytes)
+ 51       java.lang.CharacterDataLatin1::getProperties (11 bytes)
+ 52       java.lang.String::getChars (66 bytes)
+ 53       java.lang.Math::min (11 bytes)
+ 54       java.lang.AbstractStringBuilder::append (40 bytes)
+ 55       java.util.jar.Manifest$FastInputStream::readLine (167 bytes)
+ 56       net.sf.cglib.asm.ByteVector::putUTF8 (394 bytes)
+ 57       net.sf.cglib.asm.Type::a (253 bytes)
+ 58 s     java.lang.StringBuffer::append (8 bytes)
+ 59       net.sf.cglib.asm.Type::a (214 bytes)
+ 60       net.sf.cglib.asm.Type::getArgumentTypes (131 bytes)
+ 61       java.util.Arrays::copyOfRange (63 bytes)
+ 62       java.lang.AbstractStringBuilder::append (60 bytes)
+ 63       java.lang.String::<init> (72 bytes)
+ 64       java.lang.Character::isWhitespace (5 bytes)
+ 65       java.lang.Character::isWhitespace (9 bytes)
+ 66       java.lang.CharacterDataLatin1::isWhitespace (23 bytes)
+ 67       sun.reflect.generics.parser.SignatureParser::parseIdentifier (115 bytes)
+ 68       java.lang.StringBuilder::append (8 bytes)
+ 43   made not entrant  (2)  java.util.Properties$LineReader::readLine (452 bytes)
+ 69       java.lang.StringBuilder::append (8 bytes)
+  3%      java.util.Properties$LineReader::readLine @ 21 (452 bytes)
+ 70       java.util.HashMap::put (126 bytes)
+ 71       sun.reflect.generics.parser.SignatureParser::advance (37 bytes)
+ 72       java.util.zip.ZStreamRef::address (5 bytes)
+ 73       java.lang.String::compareTo (150 bytes)
+ 74       sun.misc.MetaIndex::mayContain (51 bytes)
+ 75       java.lang.String::startsWith (7 bytes)
+ 23   made zombie  (2)  java.util.HashMap::get (79 bytes)
+ 76       java.lang.AbstractStringBuilder::<init> (12 bytes)
+ 77       java.util.ArrayList$Itr::hasNext (20 bytes)
+ 78       java.util.zip.InflaterInputStream::ensureOpen (18 bytes)
+ 79       java.util.HashMap::transfer (83 bytes)
+ 80       java.util.ArrayList$Itr::checkForComodification (23 bytes)
+ 81       java.util.ArrayList$Itr::next (66 bytes)
+ 82       sun.reflect.ClassFileAssembler::emitByte (11 bytes)
+ 83       sun.reflect.ByteVectorImpl::add (38 bytes)
+ 84       java.util.ArrayList::ensureCapacity (58 bytes)
+ 85       java.util.ArrayList::add (29 bytes)
+ 86       java.util.HashMap$HashIterator::nextEntry (99 bytes)
+ 87       java.lang.Class::searchMethods (90 bytes)
+ 88       java.util.HashMap$HashIterator::<init> (63 bytes)
+ 89       java.lang.reflect.AccessibleObject::<init> (5 bytes)
+ 43   made zombie  (2)  java.util.Properties$LineReader::readLine (452 bytes)
+ 90       sun.reflect.ReflectionFactory::langReflectAccess (15 bytes)
+ 91       java.lang.Class::copyMethods (36 bytes)
+ 92       java.lang.reflect.ReflectAccess::copyMethod (5 bytes)
+ 93       java.lang.reflect.Method::copy (67 bytes)
+ 94       java.lang.reflect.Method::<init> (68 bytes)
+ 95       sun.reflect.ReflectionFactory::copyMethod (10 bytes)
+ 96       java.lang.System::getSecurityManager (4 bytes)
+ 97  !    com.sun.jersey.core.reflection.ReflectionHelper::findMethodOnClass (94 bytes)
+ 98       java.util.HashMap$HashIterator::hasNext (13 bytes)
+ 99       java.lang.StringBuilder::toString (17 bytes)
+100       java.lang.reflect.Method::equals (107 bytes)
+101       java.lang.ref.SoftReference::get (18 bytes)
+---   n   java.lang.Object::clone
+102 s     java.lang.reflect.Method::declaredAnnotations (39 bytes)
+103       java.lang.Class::clearCachesOnClassRedefinition (70 bytes)
+104       java.lang.Class::checkInitted (19 bytes)
+105       com.sun.jersey.core.reflection.AnnotatedMethod::hasParameterAnnotations (80 bytes)
+---   n   java.lang.Class::getClassLoader0
+106       java.lang.Class$MethodArray::addIfNotPresent (47 bytes)
+107       java.lang.Class::getMethod0 (97 bytes)
+---   n   java.lang.String::intern
+108       java.lang.Class::getName (20 bytes)
+109       java.util.Collections$EmptyMap::get (2 bytes)
+110       java.lang.reflect.Method::getAnnotation (26 bytes)
+111       java.util.HashMap$KeyIterator::next (8 bytes)
+110   made not entrant  (2)  java.lang.reflect.Method::getAnnotation (26 bytes)
+112       java.lang.Class::privateGetDeclaredMethods (119 bytes)
+---   n   java.lang.Class::getInterfaces
+113       com.sun.jersey.core.reflection.AnnotatedMethod::hasMethodAnnotations (43 bytes)
+114       java.lang.StringBuilder::<init> (7 bytes)
+115       java.util.AbstractCollection::<init> (5 bytes)
+116       java.lang.Character::toLowerCase (9 bytes)
+117       java.lang.CharacterDataLatin1::toLowerCase (39 bytes)
+---   n   java.lang.Class::isInterface
+118       java.lang.Class::argumentTypesToString (78 bytes)
+119       java.lang.Class::checkMemberAccess (78 bytes)
+---   n   java.lang.Class::getSuperclass
+120       sun.reflect.generics.tree.SimpleClassTypeSignature::make (11 bytes)
+121       sun.reflect.generics.tree.SimpleClassTypeSignature::<init> (20 bytes)
+122       sun.reflect.generics.parser.SignatureParser::parseClassTypeSignatureSuffix (53 bytes)
+123       sun.reflect.generics.parser.SignatureParser::parseSimpleClassTypeSignature (71 bytes)
+124       java.util.HashMap$Entry::<init> (26 bytes)
+125       sun.reflect.generics.visitor.Reifier::visitClassTypeSignature (381 bytes)
+126       java.util.Properties$LineReader::readLine (452 bytes)
+127       java.util.Properties::loadConvert (505 bytes)
+128       sun.reflect.ClassFileAssembler::emitConstantPoolUTF8 (50 bytes)
+129  !    sun.reflect.UTF8::encode (189 bytes)
+130       sun.reflect.UTF8::utf8Length (81 bytes)
+131       java.util.AbstractList::<init> (10 bytes)
+132       java.util.regex.Pattern$BmpCharProperty::match (50 bytes)
+133       java.util.regex.Matcher::search (109 bytes)
+134       java.util.regex.Pattern$Curly::match0 (174 bytes)
+132   made not entrant  (2)  java.util.regex.Pattern$BmpCharProperty::match (50 bytes)
+135       java.lang.Character::charCount (12 bytes)
+136       java.lang.Character::isHighSurrogate (18 bytes)
+137       java.lang.String::codePointAt (44 bytes)
+138       java.lang.Character::codePointAtImpl (41 bytes)
+139       java.util.regex.Pattern$Start::match (109 bytes)
+140       com.google.common.io.LineBuffer::add (201 bytes)
+141       java.util.regex.Pattern$Curly::match (86 bytes)
+142       java.util.zip.Inflater::ensureOpen (47 bytes)
+143       java.util.regex.Pattern::has (15 bytes)
+144       java.util.regex.Matcher::reset (83 bytes)
+145       java.util.regex.Pattern$Node::match (27 bytes)
+146       java.util.regex.Pattern$CharProperty::match (56 bytes)
+147       java.lang.Character::codePointAt (51 bytes)
+148       java.util.regex.Pattern$Ctype::isSatisfiedBy (24 bytes)
+149       java.util.regex.ASCII::isType (15 bytes)
+150       java.util.regex.ASCII::getType (17 bytes)
+151       java.util.regex.Pattern$Slice::match (79 bytes)
+152       java.util.regex.Pattern$Dot::isSatisfiedBy (34 bytes)
+153       java.util.regex.Matcher::match (109 bytes)
+154       java.util.regex.Pattern$BmpCharProperty::match (50 bytes)
+155       java.util.regex.Pattern$Single::isSatisfiedBy (14 bytes)
+---   n   java.lang.Thread::currentThread (static)
+156       com.google.common.collect.AbstractIndexedListIterator::hasNext (17 bytes)
+157       java.lang.String::indexOf (7 bytes)
+ 70   made not entrant  (2)  java.util.HashMap::put (126 bytes)
+133   made not entrant  (2)  java.util.regex.Matcher::search (109 bytes)
+158       java.util.regex.Matcher::search (109 bytes)
+146   made not entrant  (2)  java.util.regex.Pattern$CharProperty::match (56 bytes)
+159       java.lang.String::toUpperCase (442 bytes)
+160       java.lang.Character::toUpperCaseEx (30 bytes)
+161       java.lang.CharacterDataLatin1::toUpperCaseEx (71 bytes)
+110   made zombie  (2)  java.lang.reflect.Method::getAnnotation (26 bytes)
+162       java.nio.Buffer::position (43 bytes)
+163       java.nio.charset.CoderResult::isUnderflow (13 bytes)
+164       java.nio.Buffer::limit (62 bytes)
+165       java.nio.Buffer::<init> (121 bytes)
+166       java.nio.charset.CoderResult::isOverflow (14 bytes)
+167       java.nio.Buffer::hasRemaining (17 bytes)
+168       java.nio.CharBuffer::hasArray (20 bytes)
+169       java.nio.ByteBuffer::hasArray (20 bytes)
+170  !    java.nio.CharBuffer::wrap (20 bytes)
+171       java.nio.HeapCharBuffer::<init> (14 bytes)
+172       java.nio.CharBuffer::<init> (22 bytes)
+173  !    java.nio.charset.CharsetEncoder::encode (285 bytes)
+174       sun.nio.cs.UTF_8$Encoder::encodeLoop (28 bytes)
+175       java.nio.Buffer::remaining (10 bytes)
+176       sun.nio.cs.StreamEncoder::ensureOpen (18 bytes)
+177       sun.nio.cs.StreamEncoder::implWrite (156 bytes)
+178  !    sun.nio.cs.StreamEncoder::write (78 bytes)
+179       com.google.gson.stream.JsonWriter::string (365 bytes)
+180       java.io.OutputStreamWriter::write (9 bytes)
+181       sun.nio.cs.StreamEncoder::write (17 bytes)
+179   made not entrant  (2)  com.google.gson.stream.JsonWriter::string (365 bytes)
+182       com.google.gson.stream.JsonWriter::string (365 bytes)
+183       java.io.StringWriter::write (11 bytes)
+184 s     java.lang.StringBuffer::append (8 bytes)
+ 49   made not entrant  (2)  java.io.ByteArrayOutputStream::write (55 bytes)
+185       sun.security.util.Cache$EqualByteArray::hashCode (57 bytes)
+186       java.lang.Math::max (11 bytes)
+ 70   made zombie  (2)  java.util.HashMap::put (126 bytes)
+187 s     java.io.ByteArrayInputStream::read (36 bytes)
+188       java.math.BigInteger::stripLeadingZeroBytes (132 bytes)
+189 s     java.io.ByteArrayInputStream::available (10 bytes)
+132   made zombie  (2)  java.util.regex.Pattern$BmpCharProperty::match (50 bytes)
+190       sun.security.util.DerInputStream::available (8 bytes)
+191       java.io.ByteArrayInputStream::mark (9 bytes)
+192       java.io.DataInputStream::readUTF (501 bytes)
+193       sun.security.util.DerInputStream::getLength (111 bytes)
+194       sun.security.util.ObjectIdentifier::checkValidOid (115 bytes)
+195       sun.security.util.DerInputStream::getByte (12 bytes)
+196       sun.security.util.ObjectIdentifier::initFromEncoding (206 bytes)
+197       sun.security.util.ObjectIdentifier::getComponent (73 bytes)
+198       sun.security.util.ObjectIdentifier::equals (69 bytes)
+199       sun.security.util.ObjectIdentifier::hashCode (35 bytes)
+200       sun.security.util.DerInputStream::<init> (19 bytes)
+201  !    java.security.cert.Certificate::hashCode (34 bytes)
+202       java.lang.String::toLowerCase (436 bytes)
+203       java.lang.Character::toLowerCase (6 bytes)
+204       java.util.HashMap::addEntry (58 bytes)
+205 s     java.io.ByteArrayOutputStream::write (55 bytes)
+206       java.io.BufferedInputStream::getBufIfOpen (21 bytes)
+207 s     java.io.BufferedInputStream::read (49 bytes)
+208       java.io.DataInputStream::readChar (40 bytes)
+  4%      sun.text.normalizer.NormalizerDataReader::read @ 12 (86 bytes)
+208   made not entrant  (2)  java.io.DataInputStream::readChar (40 bytes)
+209       java.io.DataInputStream::readChar (40 bytes)
+210       java.math.BigInteger::mulAdd (81 bytes)
+  5%      com.sun.crypto.provider.AESCrypt::<clinit> @ 724 (1577 bytes)
+ 50   made not entrant  (2)  java.lang.CharacterData::of (120 bytes)
+ 64   made not entrant  (2)  java.lang.Character::isWhitespace (5 bytes)
+ 65   made not entrant  (2)  java.lang.Character::isWhitespace (9 bytes)
+ 67   made not entrant  (2)  sun.reflect.generics.parser.SignatureParser::parseIdentifier (115 bytes)
+116   made not entrant  (2)  java.lang.Character::toLowerCase (9 bytes)
+160   made not entrant  (2)  java.lang.Character::toUpperCaseEx (30 bytes)
+203   made not entrant  (2)  java.lang.Character::toLowerCase (6 bytes)
+159   made not entrant  (2)  java.lang.String::toUpperCase (442 bytes)
+202   made not entrant  (2)  java.lang.String::toLowerCase (436 bytes)
+211       java.lang.CharacterData::of (120 bytes)
+212       java.lang.String::toLowerCase (436 bytes)
+213       java.lang.String::toUpperCase (442 bytes)
+201   made not entrant  (2)  java.security.cert.Certificate::hashCode (34 bytes)
+198   made not entrant  (2)  sun.security.util.ObjectIdentifier::equals (69 bytes)
+214       java.lang.Character::toUpperCaseEx (30 bytes)
+146   made zombie  (2)  java.util.regex.Pattern$CharProperty::match (56 bytes)
+133   made zombie  (2)  java.util.regex.Matcher::search (109 bytes)
+179   made zombie  (2)  com.google.gson.stream.JsonWriter::string (365 bytes)
+215       java.lang.Character::toLowerCase (9 bytes)
+216       java.lang.Character::isWhitespace (5 bytes)
+217       java.lang.Character::isWhitespace (9 bytes)
+218       sun.text.normalizer.NormalizerImpl::decompose (680 bytes)
+219  !    sun.security.x509.AVA::toRFC2253CanonicalString (484 bytes)
+220       java.lang.String::regionMatches (157 bytes)
+221       sun.security.provider.SHA::implCompress (491 bytes)
+---   n   sun.misc.Unsafe::getInt
+ 49   made zombie  (2)  java.io.ByteArrayOutputStream::write (55 bytes)
+  6%      com.sun.crypto.provider.ARCFOURCipher::crypt @ 15 (129 bytes)
+208   made zombie  (2)  java.io.DataInputStream::readChar (40 bytes)
+222       com.sun.crypto.provider.ARCFOURCipher::crypt (129 bytes)
+223       java.lang.Integer::reverseBytes (26 bytes)
+ 45   made not entrant  (2)  java.util.HashMap::get (79 bytes)
+ 50   made zombie  (2)  java.lang.CharacterData::of (120 bytes)
+224       java.util.HashMap$EntryIterator::next (5 bytes)
+225       java.util.HashMap$EntryIterator::next (5 bytes)
+ 64   made zombie  (2)  java.lang.Character::isWhitespace (5 bytes)
+ 65   made zombie  (2)  java.lang.Character::isWhitespace (9 bytes)
+ 67   made zombie  (2)  sun.reflect.generics.parser.SignatureParser::parseIdentifier (115 bytes)
+116   made zombie  (2)  java.lang.Character::toLowerCase (9 bytes)
+226       java.util.HashMap$Entry::hashCode (38 bytes)
+227       java.util.AbstractMap::hashCode (41 bytes)
+160   made zombie  (2)  java.lang.Character::toUpperCaseEx (30 bytes)
+198   made zombie  (2)  sun.security.util.ObjectIdentifier::equals (69 bytes)
+203   made zombie  (2)  java.lang.Character::toLowerCase (6 bytes)
+201   made zombie  (2)  java.security.cert.Certificate::hashCode (34 bytes)
+159   made zombie  (2)  java.lang.String::toUpperCase (442 bytes)
+202   made zombie  (2)  java.lang.String::toLowerCase (436 bytes)
\ No newline at end of file
diff --git a/caliper/src/test/resources/com/google/caliper/bridge/jdk6-flags.txt b/caliper/src/test/resources/com/google/caliper/bridge/jdk6-flags.txt
new file mode 100644
index 0000000..8a94e73
--- /dev/null
+++ b/caliper/src/test/resources/com/google/caliper/bridge/jdk6-flags.txt
@@ -0,0 +1,770 @@
+ bool AHRByDeathCollectTimeRatio           = false            {product}
+ bool AHRByMinorPauseTimeMajorFreq         = false            {product}
+ bool AHRByPromoToAllocRatio               = false            {product}
+ bool AHRBySurvivorAge                     = false            {product}
+uintx AHRIncrementSize                     = 5242880          {product}
+uintx AHRMaxDeathCollectTimeRatio          = 55               {product}
+uintx AHRMaxMinorInvocationsPerMajor       = 30               {product}
+uintx AHRMaxMinorPauseTimeMillis           = 100              {product}
+uintx AHRMaxPromoToAllocRatio              = 25               {product}
+uintx AHRMaxRatio                          = 100              {product}
+uintx AHRMaxSize                           = 104857600        {product}
+uintx AHRMaxSurvivorAge                    = 10               {product}
+uintx AHRMinDeathCollectTimeRatio          = 50               {product}
+uintx AHRMinMinorInvocationsPerMajor       = 5                {product}
+uintx AHRMinMinorPauseTimeMillis           = 150              {product}
+uintx AHRMinPromoToAllocRatio              = 2                {product}
+uintx AHRMinSurvivorAge                    = 2                {product}
+ bool AdaptiveHeapRebalance                = false            {product}
+uintx AdaptivePermSizeWeight               = 20               {product}
+uintx AdaptiveSizeDecrementScaleFactor     = 4                {product}
+uintx AdaptiveSizeMajorGCDecayTimeScale    = 10               {product}
+uintx AdaptiveSizePausePolicy              = 0                {product}
+uintx AdaptiveSizePolicyCollectionCostMargin  = 50               {product}
+uintx AdaptiveSizePolicyInitializingSteps  = 20               {product}
+uintx AdaptiveSizePolicyOutputInterval     = 0                {product}
+uintx AdaptiveSizePolicyWeight             = 10               {product}
+uintx AdaptiveSizeThroughPutPolicy         = 0                {product}
+uintx AdaptiveTimeWeight                   = 25               {product}
+ bool AdjustConcurrency                    = false            {product}
+ bool AggressiveOpts                       = false            {product}
+ intx AliasLevel                           = 3                {product}
+ intx AllocatePrefetchDistance             = 192              {product}
+ intx AllocatePrefetchInstr                = 0                {product}
+ intx AllocatePrefetchLines                = 4                {product}
+ intx AllocatePrefetchStepSize             = 64               {product}
+ intx AllocatePrefetchStyle                = 1                {product}
+ bool AllowJNIEnvProxy                     = false            {product}
+ bool AllowParallelDefineClass             = false            {product}
+ bool AllowUserSignalHandlers              = false            {product}
+ bool AlwaysActAsServerClassMachine        = false            {product}
+ bool AlwaysCompileLoopMethods             = false            {product}
+ intx AlwaysInflate                        = 0                {product}
+ bool AlwaysLockClassLoader                = false            {product}
+ bool AlwaysPreTouch                       = false            {product}
+ bool AlwaysRestoreFPU                     = false            {product}
+ bool AlwaysTenure                         = false            {product}
+ bool AnonymousClasses                     = false            {product}
+ bool AssertOnSuspendWaitFailure           = false            {product}
+ intx Atomics                              = 0                {product}
+ intx AutoBoxCacheMax                      = 128              {C2 product}
+uintx AutoGCSelectPauseMillis              = 5000             {product}
+ intx BCEATraceLevel                       = 0                {product}
+ intx BackEdgeThreshold                    = 100000           {pd product}
+ bool BackgroundCompilation                = true             {pd product}
+uintx BaseFootPrintEstimate                = 268435456        {product}
+ intx BiasedLockingBulkRebiasThreshold     = 20               {product}
+ intx BiasedLockingBulkRevokeThreshold     = 40               {product}
+ intx BiasedLockingDecayTime               = 25000            {product}
+ intx BiasedLockingStartupDelay            = 4000             {product}
+ bool BindCMSThreadToCPU                   = false            {diagnostic}
+ bool BindGCTaskThreadsToCPUs              = false            {product}
+ bool BlockLayoutByFrequency               = true             {C2 product}
+ intx BlockLayoutMinDiamondPercentage      = 20               {C2 product}
+ bool BlockLayoutRotateLoops               = true             {C2 product}
+ bool BlockOffsetArrayUseUnallocatedBlock  = true             {product}
+ bool BranchOnRegister                     = false            {C2 product}
+ bool BytecodeVerificationLocal            = false            {product}
+ bool BytecodeVerificationRemote           = true             {product}
+ intx CICompilerCount                      = 2                {product}
+ bool CICompilerCountPerCPU                = false            {product}
+ bool CITime                               = false            {product}
+ bool CMSAbortSemantics                    = false            {product}
+uintx CMSAbortablePrecleanMinWorkPerIteration  = 100              {product}
+ intx CMSAbortablePrecleanWaitMillis       = 100              {product}
+uintx CMSBitMapYieldQuantum                = 10485760         {product}
+uintx CMSBootstrapOccupancy                = 50               {product}
+ bool CMSClassUnloadingEnabled             = false            {product}
+uintx CMSClassUnloadingMaxInterval         = 0                {product}
+ bool CMSCleanOnEnter                      = true             {product}
+ bool CMSCompactWhenClearAllSoftRefs       = true             {product}
+uintx CMSConcMarkMultiple                  = 32               {product}
+ bool CMSConcurrentMTEnabled               = true             {product}
+uintx CMSCoordinatorYieldSleepCount        = 10               {product}
+ bool CMSDumpAtPromotionFailure            = false            {product}
+uintx CMSExpAvgFactor                      = 50               {product}
+ bool CMSExtrapolateSweep                  = false            {product}
+uintx CMSFullGCsBeforeCompaction           = 0                {product}
+uintx CMSIncrementalDutyCycle              = 10               {product}
+uintx CMSIncrementalDutyCycleMin           = 0                {product}
+ bool CMSIncrementalMode                   = false            {product}
+uintx CMSIncrementalOffset                 = 0                {product}
+ bool CMSIncrementalPacing                 = true             {product}
+uintx CMSIncrementalSafetyFactor           = 10               {product}
+uintx CMSIndexedFreeListReplenish          = 4                {product}
+ intx CMSInitiatingOccupancyFraction       = -1               {product}
+ intx CMSInitiatingOccupancyFractionDelta  = -1               {product}
+ intx CMSInitiatingPermOccupancyFraction   = -1               {product}
+ intx CMSInitiatingPermOccupancyFractionDelta  = -1               {product}
+ intx CMSInitiatingRamLimitFraction        = -1               {product}
+ intx CMSIsTooFullPercentage               = 98               {product}
+double CMSLargeCoalSurplusPercent           =  {product}
+double CMSLargeSplitSurplusPercent          =  {product}
+ bool CMSLoopWarn                          = false            {product}
+uintx CMSMaxAbortablePrecleanLoops         = 0                {product}
+ intx CMSMaxAbortablePrecleanTime          = 5000             {product}
+uintx CMSOldPLABMax                        = 1024             {product}
+uintx CMSOldPLABMin                        = 16               {product}
+uintx CMSOldPLABNumRefills                 = 4                {product}
+uintx CMSOldPLABReactivityCeiling          = 10               {product}
+uintx CMSOldPLABReactivityFactor           = 2                {product}
+ bool CMSOldPLABResizeQuicker              = false            {product}
+uintx CMSOldPLABToleranceFactor            = 4                {product}
+ bool CMSPLABRecordAlways                  = true             {product}
+uintx CMSParPromoteBlocksToClaim           = 16               {product}
+ bool CMSParallelRemarkEnabled             = true             {product}
+ bool CMSParallelSurvivorRemarkEnabled     = true             {product}
+ bool CMSPermGenPrecleaningEnabled         = true             {product}
+uintx CMSPrecleanDenominator               = 3                {product}
+uintx CMSPrecleanIter                      = 3                {product}
+uintx CMSPrecleanNumerator                 = 2                {product}
+ bool CMSPrecleanRefLists1                 = true             {product}
+ bool CMSPrecleanRefLists2                 = false            {product}
+ bool CMSPrecleanSurvivors1                = false            {product}
+ bool CMSPrecleanSurvivors2                = true             {product}
+uintx CMSPrecleanThreshold                 = 1000             {product}
+ bool CMSPrecleaningEnabled                = true             {product}
+ bool CMSPrintChunksInDump                 = false            {product}
+ bool CMSPrintObjectsInDump                = false            {product}
+uintx CMSRemarkVerifyVariant               = 1                {product}
+ bool CMSReplenishIntermediate             = true             {product}
+uintx CMSRescanMultiple                    = 32               {product}
+uintx CMSRevisitStackSize                  = 1048576          {product}
+uintx CMSSamplingGrain                     = 16384            {product}
+ bool CMSScavengeBeforeRemark              = false            {product}
+uintx CMSScheduleRemarkEdenPenetration     = 50               {product}
+uintx CMSScheduleRemarkEdenSizeThreshold   = 2097152          {product}
+uintx CMSScheduleRemarkSamplingRatio       = 5                {product}
+double CMSSmallCoalSurplusPercent           =  {product}
+double CMSSmallSplitSurplusPercent          =  {product}
+ bool CMSSplitIndexedFreeListBlocks        = true             {product}
+ intx CMSTriggerPermRatio                  = 80               {product}
+ intx CMSTriggerRatio                      = 80               {product}
+ bool CMSUseGCOverheadLimit                = true             {product}
+ bool CMSUseOldDefaults                    = false            {product}
+ intx CMSWaitDuration                      = 2000             {product}
+uintx CMSWorkQueueDrainThreshold           = 10               {product}
+ bool CMSYield                             = true             {product}
+uintx CMSYieldSleepCount                   = 0                {product}
+ intx CMSYoungGenPerWorker                 = 16777216         {product}
+uintx CMS_FLSPadding                       = 1                {product}
+uintx CMS_FLSWeight                        = 75               {product}
+uintx CMS_SweepPadding                     = 1                {product}
+uintx CMS_SweepTimerThresholdMillis        = 10               {product}
+uintx CMS_SweepWeight                      = 75               {product}
+uintx CPUForCMSThread                      = 0                {diagnostic}
+ bool CheckJNICalls                        = false            {product}
+ bool ClassUnloading                       = true             {product}
+ intx ClearFPUAtPark                       = 0                {product}
+ bool ClipInlining                         = true             {product}
+uintx CodeCacheExpansionSize               = 32768            {pd product}
+uintx CodeCacheFlushingMinimumFreeSpace    = 1536000          {product}
+uintx CodeCacheMinimumFreeSpace            = 512000           {product}
+ bool CollectGen0First                     = false            {product}
+ bool CompactFields                        = true             {product}
+ intx CompilationPolicyChoice              = 0                {product}
+ intx CompilationRepeat                    = 0                {C1 product}
+ccstrlist CompileCommand                       =                  {product}
+ccstr CompileCommandFile                   =  {product}
+ccstrlist CompileOnly                          =                  {product}
+ intx CompileThreshold                     = 10000            {pd product}
+ bool CompilerThreadHintNoPreempt          = true             {product}
+ intx CompilerThreadPriority               = -1               {product}
+ intx CompilerThreadStackSize              = 0                {pd product}
+uintx ConcGCThreads                        = 0                {product}
+ intx ConditionalMoveLimit                 = 3                {C2 pd product}
+ bool ConvertSleepToYield                  = true             {pd product}
+ bool ConvertYieldToSleep                  = false            {product}
+ bool CycleTime                            = false            {product}
+ bool DTraceAllocProbes                    = false            {product}
+ bool DTraceMethodProbes                   = false            {product}
+ bool DTraceMonitorProbes                  = false            {product}
+ bool DeallocateHeapPages                  = true             {product}
+ bool DeallocateStackPages                 = true             {product}
+uintx DeallocateStackPagesMinIntervalMs    = 100              {product}
+ bool DebugContinuation                    = false            {diagnostic}
+ bool DebugInlinedCalls                    = true             {diagnostic}
+ bool DebugNonSafepoints                   = false            {diagnostic}
+uintx DefaultMaxRAMFraction                = 4                {product}
+ intx DefaultThreadPriority                = -1               {product}
+ bool DeferInitialCardMark                 = false            {diagnostic}
+ intx DeferPollingPageLoopCount            = -1               {product}
+ intx DeferThrSuspendLoopCount             = 4000             {product}
+ bool DeoptimizeRandom                     = false            {product}
+ bool DisableAttachMechanism               = false            {product}
+ bool DisableExplicitGC                    = false            {product}
+ccstrlist DisableIntrinsic                     =                  {diagnostic}
+ bool DisplayVMOutput                      = true             {diagnostic}
+ bool DisplayVMOutputToStderr              = false            {product}
+ bool DisplayVMOutputToStdout              = false            {product}
+ bool DoEscapeAnalysis                     = true             {C2 product}
+ intx DominatorSearchLimit                 = 1000             {C2 diagnostic}
+ bool DontCompileHugeMethods               = true             {product}
+ bool DontYieldALot                        = false            {pd product}
+ bool DumpSharedSpaces                     = false            {product}
+ bool EagerXrunInit                        = false            {product}
+ intx EliminateAllocationArraySizeLimit    = 64               {C2 product}
+ bool EliminateAllocations                 = true             {C2 product}
+ bool EliminateAutoBox                     = false            {C2 diagnostic}
+ bool EliminateLocks                       = true             {C2 product}
+ intx EmitSync                             = 0                {product}
+uintx ErgoHeapSizeLimit                    = 0                {product}
+ccstr ErrorFile                            =  {product}
+ bool EstimateArgEscape                    = true             {product}
+ intx EventLogLength                       = 2000             {product}
+ bool ExplicitGCInvokesConcurrent          = false            {product}
+ bool ExplicitGCInvokesConcurrentAndUnloadsClasses  = false            {product}
+ bool ExtendedDTraceProbes                 = false            {product}
+ bool FLSAlwaysCoalesceLarge               = false            {product}
+uintx FLSCoalescePolicy                    = 2                {product}
+double FLSLargestBlockCoalesceProximity     =  {product}
+ bool FLSVerifyAllHeapReferences           = false            {diagnostic}
+ bool FLSVerifyIndexTable                  = false            {diagnostic}
+ bool FLSVerifyLists                       = false            {diagnostic}
+ bool FailOverToOldVerifier                = true             {product}
+ bool FastCardTableScan                    = false            {product}
+ bool FastTLABRefill                       = true             {product}
+ intx FenceInstruction                     = 0                {product}
+ intx FieldsAllocationStyle                = 1                {product}
+ bool FilterSpuriousWakeups                = true             {product}
+ bool ForceFullGCJVMTIEpilogues            = false            {product}
+ bool ForceNUMA                            = false            {product}
+ bool ForceSharedSpaces                    = false            {product}
+ bool ForceTimeHighResolution              = false            {product}
+ intx FreqInlineSize                       = 325              {pd product}
+ bool FullProfileOnReInterpret             = true             {diagnostic}
+ intx G1ConcRefinementGreenZone            = 0                {product}
+ intx G1ConcRefinementRedZone              = 0                {product}
+ intx G1ConcRefinementServiceIntervalMillis  = 300              {product}
+uintx G1ConcRefinementThreads              = 0                {product}
+ intx G1ConcRefinementThresholdStep        = 0                {product}
+ intx G1ConcRefinementYellowZone           = 0                {product}
+ intx G1ConfidencePercent                  = 50               {product}
+uintx G1HeapRegionSize                     = 0                {product}
+ intx G1MarkRegionStackSize                = 1048576          {product}
+ bool G1PrintHeapRegions                   = false            {diagnostic}
+ intx G1RSetRegionEntries                  = 0                {product}
+uintx G1RSetScanBlockSize                  = 64               {product}
+ intx G1RSetSparseRegionEntries            = 0                {product}
+ intx G1RSetUpdatingPauseTimePercent       = 10               {product}
+ intx G1ReservePercent                     = 10               {product}
+ intx G1SATBBufferSize                     = 1024             {product}
+ bool G1SummarizeConcMark                  = false            {diagnostic}
+ bool G1SummarizeRSetStats                 = false            {diagnostic}
+ intx G1SummarizeRSetStatsPeriod           = 0                {diagnostic}
+ bool G1SummarizeZFStats                   = false            {diagnostic}
+ bool G1TraceConcRefinement                = false            {diagnostic}
+ intx G1UpdateBufferSize                   = 256              {product}
+ bool G1UseAdaptiveConcRefinement          = true             {product}
+uintx GCDrainStackTargetSize               = 64               {product}
+uintx GCHeapFreeLimit                      = 2                {product}
+ bool GCLockerInvokesConcurrent            = false            {product}
+ bool GCOverheadReporting                  = false            {product}
+ intx GCOverheadReportingPeriodMS          = 100              {product}
+ bool GCParallelVerificationEnabled        = true             {diagnostic}
+uintx GCPauseIntervalMillis                = 0                {product}
+uintx GCTaskTimeStampEntries               = 200              {product}
+uintx GCTimeLimit                          = 98               {product}
+uintx GCTimeRatio                          = 99               {product}
+ bool GoogleAgent                          = true             {product}
+ccstr GoogleAgentFlags                     =  {product}
+ bool GoogleAgentWS                        = false            {product}
+ccstr GoogleAgentWSFlags                   =  {product}
+uintx GoogleGCHeapFreeLimitPolicy          = 2                {product}
+ bool GoogleHeapInstrumentation            = false            {product}
+ bool GoogleHeapMonitor                    = true             {product}
+ bool GoogleInheritAltSigStack             = false            {product}
+uintx GoogleLogRotationSize                = 0                {diagnostic}
+uintx GoogleMaxGarbageHeapzTraces          = 200              {product}
+ bool GoogleScoping                        = false            {product}
+uintx GoogleSoftRefLRUPolicy               = 0                {product}
+uintx GoogleSoftRefLRUPolicyExcludedMB     = 0                {product}
+uintx GoogleUseConstantOMProvision         = 0                {product}
+ bool GoogleUseLibunwind                   = false            {pd product}
+ bool GoogleUseSSEForVM                    = false            {product}
+ccstr HPILibPath                           =  {product}
+ bool HandlePromotionFailure               = true             {product}
+uintx HeapBaseMinAddress                   = 2147483648       {pd product}
+ bool HeapDumpAfterFullGC                  = false            {manageable}
+ bool HeapDumpBeforeFullGC                 = false            {manageable}
+ bool HeapDumpOnOutOfMemoryError           = false            {manageable}
+ccstr HeapDumpPath                         =  {manageable}
+uintx HeapFirstMaximumCompactionCount      = 3                {product}
+uintx HeapMaximumCompactionInterval        = 20               {product}
+ bool HoistFinalLoads                      = false            {product}
+ bool IgnoreUnrecognizedVMOptions          = false            {product}
+uintx InitialCodeCacheSize                 = 2359296          {pd product}
+ bool InitialCompileFast                   = false            {diagnostic}
+ bool InitialCompileReallyFast             = false            {diagnostic}
+uintx InitialHeapSize                     := 67108864         {product}
+uintx InitialRAMFraction                   = 64               {product}
+uintx InitialSurvivorRatio                 = 8                {product}
+ intx InitialTenuringThreshold             = 7                {product}
+uintx InitiatingHeapOccupancyPercent       = 45               {product}
+ bool Inline                               = true             {product}
+ bool InlineMethodsWithNullUnloadedTypesInSignature  = true             {product}
+ intx InlineSmallCode                      = 1000             {pd product}
+ bool InsertMemBarAfterArraycopy           = true             {C2 product}
+ intx InteriorEntryAlignment               = 4                {C2 pd product}
+ intx InterpreterProfilePercentage         = 33               {product}
+ bool JNIDetachReleasesMonitors            = true             {product}
+ bool JavaMonitorsInStackTrace             = true             {product}
+ intx JavaPriority10_To_OSPriority         = -1               {product}
+ intx JavaPriority1_To_OSPriority          = -1               {product}
+ intx JavaPriority2_To_OSPriority          = -1               {product}
+ intx JavaPriority3_To_OSPriority          = -1               {product}
+ intx JavaPriority4_To_OSPriority          = -1               {product}
+ intx JavaPriority5_To_OSPriority          = -1               {product}
+ intx JavaPriority6_To_OSPriority          = -1               {product}
+ intx JavaPriority7_To_OSPriority          = -1               {product}
+ intx JavaPriority8_To_OSPriority          = -1               {product}
+ intx JavaPriority9_To_OSPriority          = -1               {product}
+ bool LIRFillDelaySlots                    = false            {C1 pd product}
+uintx LargePageHeapSizeThreshold           = 134217728        {product}
+ccstr LargePageMountPoint                  =                  {product}
+uintx LargePageSizeInBytes                 = 0                {product}
+ bool LazyBootClassLoader                  = true             {product}
+ bool LinkWellKnownClasses                 = false            {diagnostic}
+ bool LogAHR                               = false            {product}
+ bool LogCompilation                       = false            {diagnostic}
+ccstr LogFile                              =  {diagnostic}
+ bool LogGCOverheadLimit                   = false            {product}
+ bool LogVMOutput                          = false            {diagnostic}
+ intx LoopOptsCount                        = 43               {C2 product}
+ intx LoopUnrollLimit                      = 50               {C2 pd product}
+ intx LoopUnrollMin                        = 4                {C2 product}
+ bool LoopUnswitching                      = true             {C2 product}
+ intx MallocVerifyInterval                 = 0                {diagnostic}
+ intx MallocVerifyStart                    = 0                {diagnostic}
+ bool ManagementServer                     = false            {product}
+uintx MarkStackSize                        = 32768            {product}
+uintx MarkStackSizeMax                     = 4194304          {product}
+ intx MarkSweepAlwaysCompactCount          = 4                {product}
+uintx MarkSweepDeadRatio                   = 5                {product}
+ intx MaxBCEAEstimateLevel                 = 5                {product}
+ intx MaxBCEAEstimateSize                  = 150              {product}
+ intx MaxDirectMemorySize                  = -1               {product}
+ bool MaxFDLimit                           = true             {product}
+uintx MaxGCMinorPauseMillis                = 4294967295       {product}
+uintx MaxGCPauseMillis                     = 4294967295       {product}
+uintx MaxHeapFreeRatio                     = 70               {product}
+uintx MaxHeapSize                         := 1073741824       {product}
+ intx MaxInlineLevel                       = 9                {product}
+ intx MaxInlineSize                        = 35               {product}
+ intx MaxJavaStackTraceDepth               = 1024             {product}
+ intx MaxJumpTableSize                     = 65000            {C2 product}
+ intx MaxJumpTableSparseness               = 5                {C2 product}
+ intx MaxLabelRootDepth                    = 1100             {C2 product}
+uintx MaxLiveObjectEvacuationRatio         = 100              {product}
+ intx MaxLoopPad                           = 11               {C2 product}
+uintx MaxNewSize                           = 4294901760       {product}
+ intx MaxNodeLimit                         = 65000            {C2 product}
+uintx MaxPermHeapExpansion                 = 4194304          {product}
+uintx MaxPermSize                          = 67108864         {pd product}
+uint64_t MaxRAM                               = 0                {pd product}
+uintx MaxRAMFraction                       = 4                {product}
+ intx MaxRecursiveInlineLevel              = 1                {product}
+ intx MaxTenuringThreshold                 = 15               {product}
+ intx MaxTrivialSize                       = 6                {product}
+ bool MethodFlushing                       = true             {product}
+ intx MethodHandlePushLimit                = 3                {diagnostic}
+ intx MinCodeCacheFlushingInterval         = 30               {product}
+uintx MinHeapDeltaBytes                    = 131072           {product}
+uintx MinHeapFreeRatio                     = 40               {product}
+ intx MinInliningThreshold                 = 250              {product}
+ intx MinJumpTableSize                     = 18               {C2 product}
+uintx MinPermHeapExpansion                 = 262144           {product}
+uintx MinRAMFraction                       = 2                {product}
+uintx MinSurvivorRatio                     = 3                {product}
+uintx MinTLABSize                          = 2048             {product}
+ bool MixedModeThreadDump                  = true             {product}
+ intx MonitorBound                         = 0                {product}
+ bool MonitorInUseLists                    = false            {product}
+ intx MultiArrayExpandLimit                = 6                {C2 product}
+ bool MustCallLoadClassInternal            = false            {product}
+ intx NUMAChunkResizeWeight                = 20               {product}
+ intx NUMAPageScanRate                     = 256              {product}
+ intx NUMASpaceResizeRate                  = 1073741824       {product}
+ bool NUMAStats                            = false            {product}
+ intx NativeMonitorFlags                   = 0                {product}
+ intx NativeMonitorSpinLimit               = 20               {product}
+ intx NativeMonitorTimeout                 = -1               {product}
+ bool NeedsDeoptSuspend                    = false            {pd product}
+ bool NeverActAsServerClassMachine         = false            {pd product}
+ bool NeverTenure                          = false            {product}
+ intx NewRatio                             = 2                {product}
+uintx NewSize                              = 1048576          {product}
+uintx NewSizeThreadIncrease                = 4096             {pd product}
+ intx NmethodSweepCheckInterval            = 5                {product}
+ intx NmethodSweepFraction                 = 4                {product}
+ intx NodeLimitFudgeFactor                 = 1000             {C2 product}
+ intx NumberOfLoopInstrToAlign             = 4                {C2 product}
+ bool ObjLifeTracking                      = false            {product}
+uintx ObjLifeTrackingCpuFraction           = 100              {product}
+uintx ObjLifetimeHistoBucketCount          = 1024             {product}
+ bool ObjLifetimeHistoExportVarPerBucket   = false            {product}
+uintx OldPLABSize                          = 1024             {product}
+uintx OldPLABWeight                        = 50               {product}
+uintx OldSize                              = 4194304          {product}
+ bool OmitStackTraceInFastThrow            = true             {product}
+ccstrlist OnError                              =                  {product}
+ccstrlist OnOutOfMemoryError                   =                  {product}
+ intx OnStackReplacePercentage             = 140              {pd product}
+ bool OptimizeFill                         = false            {C2 product}
+ bool OptimizeMethodHandles                = true             {diagnostic}
+ bool OptimizeStringConcat                 = false            {C2 product}
+ bool OptoBundling                         = false            {C2 pd product}
+ intx OptoLoopAlignment                    = 16               {pd product}
+ bool OptoScheduling                       = false            {C2 pd product}
+uintx PLABWeight                           = 75               {product}
+ bool PSChunkLargeArrays                   = true             {product}
+ bool PSResizeByFreeRatio                  = false            {product}
+ bool PSResizeByFreeRatioWithSystemGC      = false            {product}
+ intx ParGCArrayScanChunk                  = 50               {product}
+ intx ParGCCardsPerStrideChunk             = 256              {diagnostic}
+uintx ParGCDesiredObjsFromOverflowList     = 20               {product}
+ intx ParGCStridesPerThread                = 2                {diagnostic}
+ bool ParGCTrimOverflow                    = true             {product}
+ bool ParGCUseLocalOverflow                = false            {product}
+ intx ParallelGCBufferWastePct             = 10               {product}
+ bool ParallelGCRetainPLAB                 = true             {product}
+uintx ParallelGCThreads                   := 10               {product}
+ bool ParallelGCVerbose                    = false            {product}
+uintx ParallelOldDeadWoodLimiterMean       = 50               {product}
+uintx ParallelOldDeadWoodLimiterStdDev     = 80               {product}
+ bool ParallelRefProcBalancingEnabled      = true             {product}
+ bool ParallelRefProcEnabled               = false            {product}
+ bool PartialPeelAtUnsignedTests           = true             {C2 product}
+ bool PartialPeelLoop                      = true             {C2 product}
+ intx PartialPeelNewPhiDelta               = 0                {C2 product}
+ bool PauseAtStartup                       = false            {diagnostic}
+ccstr PauseAtStartupFile                   =  {diagnostic}
+uintx PausePadding                         = 1                {product}
+ intx PerBytecodeRecompilationCutoff       = 200              {product}
+ intx PerBytecodeTrapLimit                 = 4                {product}
+ intx PerMethodRecompilationCutoff         = 400              {product}
+ intx PerMethodTrapLimit                   = 100              {product}
+ bool PerfAllowAtExitRegistration          = false            {product}
+ bool PerfBypassFileSystemCheck            = false            {product}
+ intx PerfDataMemorySize                   = 32768            {product}
+ intx PerfDataSamplingInterval             = 50               {product}
+ccstr PerfDataSaveFile                     =  {product}
+ bool PerfDataSaveToFile                   = false            {product}
+ bool PerfDisableSharedMem                 = false            {product}
+ intx PerfMaxStringConstLength             = 1024             {product}
+uintx PermGenPadding                       = 3                {product}
+uintx PermMarkSweepDeadRatio               = 20               {product}
+uintx PermSize                             = 16777216         {pd product}
+ bool PostSpinYield                        = true             {product}
+ intx PreBlockSpin                         = 10               {product}
+ intx PreInflateSpin                       = 10               {pd product}
+ bool PreSpinYield                         = false            {product}
+ bool PreferInterpreterNativeStubs         = false            {pd product}
+ intx PrefetchCopyIntervalInBytes          = -1               {product}
+ intx PrefetchFieldsAhead                  = -1               {product}
+ intx PrefetchScanIntervalInBytes          = -1               {product}
+ bool PreserveAllAnnotations               = false            {product}
+uintx PreserveMarkStackSize                = 1024             {product}
+uintx PretenureSizeThreshold               = 0                {product}
+ bool PrintAdapterHandlers                 = false            {diagnostic}
+ bool PrintAdaptiveSizePolicy              = false            {product}
+ bool PrintAssembly                        = false            {diagnostic}
+ccstr PrintAssemblyOptions                 =  {diagnostic}
+ bool PrintBiasedLockingStatistics         = false            {diagnostic}
+ bool PrintCMSInitiationCause              = false            {product}
+ bool PrintCMSInitiationStatistics         = false            {product}
+ intx PrintCMSStatistics                   = 0                {product}
+ bool PrintCardTableStats                  = false            {product}
+ bool PrintClassHistogram                  = false            {manageable}
+ bool PrintClassHistogramAfterFullGC       = false            {manageable}
+ bool PrintClassHistogramBeforeFullGC      = false            {manageable}
+ bool PrintCommandLineFlags                = false            {product}
+ bool PrintCompilation                     = false            {product}
+ bool PrintCompressedOopsMode              = false            {diagnostic}
+ bool PrintConcurrentLocks                 = false            {manageable}
+ bool PrintDTraceDOF                       = false            {diagnostic}
+ intx PrintFLSCensus                       = 0                {product}
+ intx PrintFLSStatistics                   = 0                {product}
+ bool PrintFlagsFinal                     := true             {product}
+ bool PrintFlagsInitial                    = false            {product}
+ bool PrintGC                              = false            {manageable}
+ bool PrintGCApplicationConcurrentTime     = false            {product}
+ bool PrintGCApplicationStoppedTime        = false            {product}
+ bool PrintGCDateStamps                    = false            {manageable}
+ bool PrintGCDetails                       = false            {manageable}
+ bool PrintGCTaskTimeStamps                = false            {product}
+ bool PrintGCTimeStamps                    = false            {manageable}
+ bool PrintHeapAtGC                        = false            {product rw}
+ bool PrintHeapAtGCExtended                = false            {product rw}
+ bool PrintHeapAtSIGBREAK                  = true             {product}
+ bool PrintInlining                        = false            {diagnostic}
+ bool PrintInterpreter                     = false            {diagnostic}
+ bool PrintIntrinsics                      = false            {diagnostic}
+ bool PrintJNIGCStalls                     = false            {product}
+ bool PrintJNIResolving                    = false            {product}
+ bool PrintNMethods                        = false            {diagnostic}
+ bool PrintNativeNMethods                  = false            {diagnostic}
+ bool PrintOldPLAB                         = false            {product}
+ bool PrintOopAddress                      = false            {product}
+ bool PrintPLAB                            = false            {product}
+ bool PrintParallelOldGCPhaseTimes         = false            {product}
+ bool PrintPreciseBiasedLockingStatistics  = false            {C2 diagnostic}
+ bool PrintPromotionFailure                = false            {product}
+ bool PrintReferenceGC                     = false            {product}
+ bool PrintRevisitStats                    = false            {product}
+ bool PrintSafepointStatistics             = false            {product}
+ intx PrintSafepointStatisticsCount        = 300              {product}
+ intx PrintSafepointStatisticsTimeout      = -1               {product}
+ bool PrintSharedSpaces                    = false            {product}
+ bool PrintSignatureHandlers               = false            {diagnostic}
+ bool PrintStubCode                        = false            {diagnostic}
+ bool PrintTLAB                            = false            {product}
+ bool PrintTenuringDistribution            = false            {product}
+ bool PrintVMOptions                       = false            {product}
+ bool PrintVMQWaitTime                     = false            {product}
+uintx ProcessDistributionStride            = 4                {product}
+ bool ProfileInterpreter                   = true             {pd product}
+ bool ProfileIntervals                     = false            {product}
+ intx ProfileIntervalsTicks                = 100              {product}
+ intx ProfileMaturityPercentage            = 20               {product}
+ bool ProfileVM                            = false            {product}
+ bool ProfilerPrintByteCodeStatistics      = false            {product}
+ bool ProfilerRecordPC                     = false            {product}
+uintx PromotedPadding                      = 3                {product}
+ intx QueuedAllocationWarningCount         = 0                {product}
+uintx RamLimit                             = 0                {product}
+ bool RangeCheckElimination                = true             {product}
+ intx ReadPrefetchInstr                    = 0                {product}
+ intx ReadSpinIterations                   = 100              {product}
+ bool ReassociateInvariants                = true             {C2 product}
+ bool ReduceBulkZeroing                    = true             {C2 product}
+ bool ReduceFieldZeroing                   = true             {C2 product}
+ bool ReduceInitialCardMarks               = true             {C2 product}
+ bool ReduceSignalUsage                    = false            {product}
+ intx RefDiscoveryPolicy                   = 0                {product}
+ bool ReflectionWrapResolutionErrors       = true             {product}
+ bool RegisterFinalizersAtInit             = true             {product}
+ bool RelaxAccessControlCheck              = false            {product}
+ bool RequireSharedSpaces                  = false            {product}
+uintx ReservedCodeCacheSize                = 50331648         {pd product}
+ bool ResizeOldPLAB                        = true             {product}
+ bool ResizePLAB                           = true             {product}
+ bool ResizeTLAB                           = true             {pd product}
+ bool RestoreMXCSROnJNICalls               = false            {product}
+ bool RewriteBytecodes                     = true             {pd product}
+ bool RewriteFrequentPairs                 = true             {pd product}
+ intx SafepointPollOffset                  = 256              {C1 pd product}
+ intx SafepointSpinBeforeYield             = 2000             {product}
+ bool SafepointTimeout                     = false            {product}
+ intx SafepointTimeoutDelay                = 10000            {product}
+ bool ScavengeBeforeFullGC                 = true             {product}
+ intx ScavengeRootsInCode                  = 0                {diagnostic}
+ intx SelfDestructTimer                    = 0                {product}
+ bool SerializeVMOutput                    = true             {diagnostic}
+uintx SharedDummyBlockSize                 = 536870912        {product}
+uintx SharedMiscCodeSize                   = 4194304          {product}
+uintx SharedMiscDataSize                   = 4194304          {product}
+ bool SharedOptimizeColdStart              = true             {diagnostic}
+uintx SharedReadOnlySize                   = 10485760         {product}
+uintx SharedReadWriteSize                  = 12582912         {product}
+ bool SharedSkipVerify                     = false            {diagnostic}
+ bool ShowMessageBoxOnError                = false            {product}
+ intx SoftRefLRUPolicyMSPerMB              = 1000             {product}
+ bool SplitIfBlocks                        = true             {product}
+ intx StackRedPages                        = 1                {pd product}
+ intx StackShadowPages                     = 3                {pd product}
+ bool StackTraceInThrowable                = true             {product}
+ intx StackYellowPages                     = 2                {pd product}
+ bool StartAttachListener                  = false            {product}
+ bool StartSuspended                       = false            {product}
+ intx StarvationMonitorInterval            = 200              {product}
+ bool StressLdcRewrite                     = false            {product}
+ bool StressTieredRuntime                  = false            {product}
+ bool SuppressFatalErrorMessage            = false            {product}
+uintx SurvivorPadding                      = 3                {product}
+ intx SurvivorRatio                        = 8                {product}
+ intx SuspendRetryCount                    = 50               {product}
+ intx SuspendRetryDelay                    = 5                {product}
+ intx SyncFlags                            = 0                {product}
+ccstr SyncKnobs                            =  {product}
+ intx SyncVerbose                          = 0                {product}
+uintx TLABAllocationWeight                 = 35               {product}
+uintx TLABRefillWasteFraction              = 64               {product}
+uintx TLABSize                             = 0                {product}
+ bool TLABStats                            = true             {product}
+uintx TLABWasteIncrement                   = 4                {product}
+uintx TLABWasteTargetPercent               = 1                {product}
+ intx TargetPLABWastePct                   = 10               {product}
+ intx TargetSurvivorRatio                  = 50               {product}
+uintx TenuredGenerationSizeIncrement       = 20               {product}
+uintx TenuredGenerationSizeSupplement      = 80               {product}
+uintx TenuredGenerationSizeSupplementDecay  = 2                {product}
+ intx ThreadPriorityPolicy                 = 0                {product}
+ bool ThreadPriorityVerbose                = false            {product}
+uintx ThreadSafetyMargin                   = 52428800         {product}
+ intx ThreadStackSize                      = 320              {pd product}
+uintx ThresholdTolerance                   = 10               {product}
+ intx Tier1BytecodeLimit                   = 10               {product}
+ intx Tier1FreqInlineSize                  = 35               {C2 product}
+ intx Tier1Inline                          = 0                {C2 product}
+ intx Tier1LoopOptsCount                   = 0                {C2 product}
+ intx Tier1MaxInlineSize                   = 8                {C2 product}
+ bool Tier1OptimizeVirtualCallProfiling    = true             {C1 product}
+ bool Tier1ProfileBranches                 = true             {C1 product}
+ bool Tier1ProfileCalls                    = true             {C1 product}
+ bool Tier1ProfileCheckcasts               = true             {C1 product}
+ bool Tier1ProfileInlinedCalls             = true             {C1 product}
+ bool Tier1ProfileVirtualCalls             = true             {C1 product}
+ bool Tier1UpdateMethodData                = true             {product}
+ intx Tier2BackEdgeThreshold               = 100000           {pd product}
+ intx Tier2CompileThreshold                = 10000            {pd product}
+ intx Tier3BackEdgeThreshold               = 100000           {pd product}
+ intx Tier3CompileThreshold                = 20000            {pd product}
+ intx Tier4BackEdgeThreshold               = 100000           {pd product}
+ intx Tier4CompileThreshold                = 40000            {pd product}
+ bool TieredCompilation                    = false            {pd product}
+ bool TimeLinearScan                       = false            {C1 product}
+ bool TraceBiasedLocking                   = false            {product}
+ bool TraceClassLoading                    = false            {product rw}
+ bool TraceClassLoadingPreorder            = false            {product}
+ bool TraceClassResolution                 = false            {product}
+ bool TraceClassUnloading                  = false            {product rw}
+ bool TraceCompileTriggered                = false            {diagnostic}
+ bool TraceGen0Time                        = false            {product}
+ bool TraceGen1Time                        = false            {product}
+ccstr TraceJVMTI                           =  {product}
+ bool TraceJVMTIObjectTagging              = false            {diagnostic}
+ bool TraceLoaderConstraints               = false            {product rw}
+ bool TraceMonitorInflation                = false            {product}
+ bool TraceNMethodInstalls                 = false            {diagnostic}
+ bool TraceOSRBreakpoint                   = false            {diagnostic}
+ bool TraceParallelOldGCTasks              = false            {product}
+ intx TraceRedefineClasses                 = 0                {product}
+ bool TraceRedundantCompiles               = false            {diagnostic}
+ bool TraceSafepointCleanupTime            = false            {product}
+ bool TraceSuperWord                       = false            {C2 product}
+ bool TraceSuspendWaitFailures             = false            {product}
+ bool TraceTriggers                        = false            {diagnostic}
+ intx TrackedInitializationLimit           = 50               {C2 product}
+ intx TypeProfileMajorReceiverPercent      = 90               {product}
+ intx TypeProfileWidth                     = 2                {product}
+ intx UnguardOnExecutionViolation          = 0                {product}
+ bool UnlockDiagnosticVMOptions            = true             {diagnostic}
+ bool UnsyncloadClass                      = false            {diagnostic}
+ bool Use486InstrsOnly                     = false            {product}
+ bool UseAdaptiveGCBoundary                = false            {product}
+ bool UseAdaptiveGenerationSizePolicyAtMajorCollection  = true             {product}
+ bool UseAdaptiveGenerationSizePolicyAtMinorCollection  = true             {product}
+ bool UseAdaptiveNUMAChunkSizing           = true             {product}
+ bool UseAdaptiveSizeDecayMajorGCCost      = true             {product}
+ bool UseAdaptiveSizePolicy                = true             {product}
+ bool UseAdaptiveSizePolicyFootprintGoal   = true             {product}
+ bool UseAdaptiveSizePolicyWithSystemGC    = false            {product}
+ bool UseAddressNop                        = true             {product}
+ bool UseAltSigs                           = false            {product}
+ bool UseAutoGCSelectPolicy                = false            {product}
+ bool UseBiasedLocking                     = true             {product}
+ bool UseBimorphicInlining                 = true             {C2 product}
+ bool UseBoundThreads                      = true             {product}
+ bool UseCMSBestFit                        = true             {product}
+ bool UseCMSCollectionPassing              = true             {product}
+ bool UseCMSCompactAtFullCollection        = true             {product}
+ bool UseCMSInitiatingOccupancyOnly        = false            {product}
+ bool UseCodeCacheFlushing                 = false            {product}
+ bool UseCompiler                          = true             {product}
+ bool UseCompilerSafepoints                = true             {product}
+ bool UseConcMarkSweepGC                   = false            {product}
+ bool UseCountLeadingZerosInstruction      = false            {product}
+ bool UseCounterDecay                      = true             {product}
+ bool UseDivMod                            = true             {C2 product}
+ bool UseFPUForSpilling                    = false            {C2 product}
+ bool UseFastAccessorMethods               = true             {product}
+ bool UseFastEmptyMethods                  = true             {product}
+ bool UseFastJNIAccessors                  = true             {product}
+ bool UseG1GC                              = false            {product}
+ bool UseGCOverheadLimit                   = true             {product}
+ bool UseGCTaskAffinity                    = false            {product}
+ bool UseHeavyMonitors                     = false            {product}
+ bool UseIncDec                            = true             {diagnostic}
+ bool UseInlineCaches                      = true             {product}
+ bool UseInterpreter                       = true             {product}
+ bool UseJumpTables                        = true             {C2 product}
+ bool UseLWPSynchronization                = true             {product}
+ bool UseLargePages                        = false            {pd product}
+ bool UseLargePagesIndividualAllocation    = false            {pd product}
+ bool UseLinuxPosixThreadCPUClocks         = false            {product}
+ bool UseLoopCounter                       = true             {product}
+ bool UseLoopPredicate                     = true             {C2 product}
+ bool UseMaximumCompactionOnSystemGC       = true             {product}
+ bool UseMembar                            = false            {product}
+ bool UseNUMA                              = false            {product}
+ bool UseNewCode                           = false            {diagnostic}
+ bool UseNewCode2                          = false            {diagnostic}
+ bool UseNewCode3                          = false            {diagnostic}
+ bool UseNewFeature1                       = false            {C1 product}
+ bool UseNewFeature2                       = false            {C1 product}
+ bool UseNewFeature3                       = false            {C1 product}
+ bool UseNewFeature4                       = false            {C1 product}
+ bool UseNewLongLShift                     = false            {product}
+ bool UseNiagaraInstrs                     = false            {product}
+ bool UseOSErrorReporting                  = false            {pd product}
+ bool UseOldInlining                       = true             {C2 product}
+ bool UseOnStackReplacement                = true             {pd product}
+ bool UseOnlyInlinedBimorphic              = true             {C2 product}
+ bool UseOprofile                          = false            {product}
+ bool UseOptoBiasInlining                  = true             {C2 product}
+ bool UsePSAdaptiveSurvivorSizePolicy      = true             {product}
+ bool UseParNewGC                          = false            {product}
+ bool UseParallelDensePrefixUpdate         = true             {product}
+ bool UseParallelGC                       := true             {product}
+ bool UseParallelOldGC                     = false            {product}
+ bool UseParallelOldGCCompacting           = true             {product}
+ bool UseParallelOldGCDensePrefix          = true             {product}
+ bool UsePerfData                          = true             {product}
+ bool UsePopCountInstruction               = true             {product}
+ intx UseSSE                               = 4                {product}
+ bool UseSSE42Intrinsics                   = true             {product}
+ bool UseSeparateVSpacesInYoungGen         = true             {product}
+ bool UseSerialGC                          = false            {product}
+ bool UseSharedSpaces                      = false            {product}
+ bool UseSignalChaining                    = true             {product}
+ bool UseSpinning                          = false            {product}
+ bool UseSplitVerifier                     = true             {product}
+ bool UseStoreImmI16                       = false            {product}
+ bool UseStringCache                       = false            {product}
+ bool UseSuperWord                         = true             {C2 product}
+ bool UseTLAB                              = true             {pd product}
+ bool UseThreadPriorities                  = true             {pd product}
+ bool UseTypeProfile                       = true             {product}
+ bool UseUnalignedLoadStores               = true             {product}
+ bool UseVMInterruptibleIO                 = true             {product}
+ bool UseVectoredExceptions                = false            {pd product}
+ bool UseXMMForArrayCopy                   = true             {product}
+ bool UseXmmI2D                            = false            {product}
+ bool UseXmmI2F                            = false            {product}
+ bool UseXmmLoadAndClearUpper              = true             {product}
+ bool UseXmmRegToRegMoveAll                = true             {product}
+ bool VMThreadHintNoPreempt                = false            {product}
+ intx VMThreadPriority                     = -1               {product}
+ intx VMThreadStackSize                    = 512              {pd product}
+ intx ValueMapInitialSize                  = 11               {C1 product}
+ intx ValueMapMaxLoopSize                  = 8                {C1 product}
+ intx ValueSearchLimit                     = 1000             {C2 product}
+ bool VerifyAfterGC                        = false            {diagnostic}
+ bool VerifyBeforeExit                     = false            {diagnostic}
+ bool VerifyBeforeGC                       = false            {diagnostic}
+ bool VerifyBeforeIteration                = false            {diagnostic}
+ bool VerifyDuringGC                       = false            {diagnostic}
+ intx VerifyGCLevel                        = 0                {diagnostic}
+uintx VerifyGCStartAt                      = 0                {diagnostic}
+ bool VerifyMergedCPBytecodes              = true             {product}
+ bool VerifyMethodHandles                  = false            {diagnostic}
+ bool VerifyObjectStartArray               = true             {diagnostic}
+ bool VerifyRememberedSets                 = false            {diagnostic}
+ intx WorkAroundNPTLTimedWaitHang          = 1                {product}
+uintx YoungGenerationSizeIncrement         = 20               {product}
+uintx YoungGenerationSizeSupplement        = 80               {product}
+uintx YoungGenerationSizeSupplementDecay   = 8                {product}
+uintx YoungPLABSize                        = 4096             {product}
+ bool ZeroTLAB                             = false            {product}
+ intx hashCode                             = 0                {product}
\ No newline at end of file
diff --git a/caliper/src/test/resources/com/google/caliper/bridge/jdk6-gc.txt b/caliper/src/test/resources/com/google/caliper/bridge/jdk6-gc.txt
new file mode 100644
index 0000000..fd9d07c
--- /dev/null
+++ b/caliper/src/test/resources/com/google/caliper/bridge/jdk6-gc.txt
@@ -0,0 +1,200 @@
+[GC 987K->384K(62848K), 0.0012320 secs]
+[Full GC 384K->288K(62848K), 0.0054550 secs]
+[GC 288K->288K(62848K), 0.0004450 secs]
+[Full GC 288K->288K(62848K), 0.0049580 secs]
+[GC 288K->288K(62848K), 0.0004590 secs]
+[Full GC 288K->288K(62848K), 0.0048240 secs]
+[GC 288K->288K(62848K), 0.0005700 secs]
+[Full GC 288K->288K(62848K), 0.0063250 secs]
+[GC 288K->288K(62848K), 0.0003540 secs]
+[Full GC 288K->288K(62848K), 0.0048210 secs]
+[GC 288K->288K(62848K), 0.0003700 secs]
+[Full GC 288K->288K(62848K), 0.0049430 secs]
+[GC 288K->288K(62848K), 0.0004250 secs]
+[Full GC 288K->288K(62848K), 0.0047700 secs]
+[GC 288K->288K(62848K), 0.0003550 secs]
+[Full GC 288K->288K(62848K), 0.0047090 secs]
+[GC 288K->288K(62848K), 0.0003850 secs]
+[Full GC 288K->288K(62848K), 0.0047220 secs]
+[GC 288K->288K(62848K), 0.0003830 secs]
+[Full GC 288K->288K(62848K), 0.0047330 secs]
+[GC 288K->288K(62848K), 0.0004230 secs]
+[Full GC 288K->288K(62848K), 0.0047130 secs]
+[GC 288K->288K(62848K), 0.0003140 secs]
+[Full GC 288K->288K(62848K), 0.0047360 secs]
+[GC 288K->288K(62848K), 0.0003540 secs]
+[Full GC 288K->288K(62848K), 0.0047210 secs]
+[GC 288K->288K(62848K), 0.0003610 secs]
+[Full GC 288K->288K(62848K), 0.0047280 secs]
+[GC 288K->288K(62848K), 0.0004640 secs]
+[Full GC 288K->288K(62848K), 0.0046920 secs]
+[GC 288K->288K(62848K), 0.0004700 secs]
+[Full GC 288K->288K(62848K), 0.0047080 secs]
+[GC 288K->288K(62848K), 0.0003220 secs]
+[Full GC 288K->288K(62848K), 0.0047020 secs]
+[GC 288K->288K(62848K), 0.0004100 secs]
+[Full GC 288K->288K(62848K), 0.0047860 secs]
+[GC 288K->288K(62848K), 0.0004340 secs]
+[Full GC 288K->288K(62848K), 0.0047310 secs]
+[GC 288K->288K(62848K), 0.0003980 secs]
+[Full GC 288K->288K(62848K), 0.0047440 secs]
+[GC 288K->288K(62848K), 0.0004540 secs]
+[Full GC 288K->288K(62848K), 0.0047260 secs]
+[GC 288K->288K(62848K), 0.0003340 secs]
+[Full GC 288K->288K(62848K), 0.0047130 secs]
+[GC 288K->288K(62848K), 0.0003420 secs]
+[Full GC 288K->288K(62848K), 0.0046830 secs]
+[GC 288K->288K(62848K), 0.0003560 secs]
+[Full GC 288K->288K(62848K), 0.0047030 secs]
+[GC 288K->288K(62848K), 0.0003440 secs]
+[Full GC 288K->288K(62848K), 0.0047340 secs]
+[GC 288K->288K(62848K), 0.0004980 secs]
+[Full GC 288K->288K(62848K), 0.0048440 secs]
+[GC 288K->288K(62848K), 0.0003500 secs]
+[Full GC 288K->288K(62848K), 0.0048000 secs]
+[GC 288K->288K(62848K), 0.0004090 secs]
+[Full GC 288K->288K(62848K), 0.0048140 secs]
+[GC 288K->288K(62848K), 0.0004960 secs]
+[Full GC 288K->288K(62848K), 0.0048130 secs]
+[GC 288K->288K(62848K), 0.0003140 secs]
+[Full GC 288K->288K(62848K), 0.0047990 secs]
+[GC 288K->288K(62848K), 0.0003350 secs]
+[Full GC 288K->288K(62848K), 0.0048460 secs]
+[GC 288K->288K(62848K), 0.0002190 secs]
+[Full GC 288K->288K(62848K), 0.0047700 secs]
+[GC 288K->288K(62848K), 0.0003200 secs]
+[Full GC 288K->288K(62848K), 0.0047690 secs]
+[GC 288K->288K(62848K), 0.0003220 secs]
+[Full GC 288K->288K(62848K), 0.0047490 secs]
+[GC 288K->288K(62848K), 0.0003210 secs]
+[Full GC 288K->288K(62848K), 0.0047590 secs]
+[GC 288K->288K(62848K), 0.0002560 secs]
+[Full GC 288K->288K(62848K), 0.0047250 secs]
+[GC 288K->288K(62848K), 0.0003560 secs]
+[Full GC 288K->288K(62848K), 0.0047430 secs]
+[GC 288K->288K(62848K), 0.0002860 secs]
+[Full GC 288K->288K(62848K), 0.0047320 secs]
+[GC 288K->288K(62848K), 0.0003470 secs]
+[Full GC 288K->288K(62848K), 0.0047370 secs]
+[GC 288K->288K(62848K), 0.0003020 secs]
+[Full GC 288K->288K(62848K), 0.0047140 secs]
+[GC 288K->288K(62848K), 0.0002670 secs]
+[Full GC 288K->288K(62848K), 0.0047510 secs]
+[GC 288K->288K(62848K), 0.0003510 secs]
+[Full GC 288K->288K(62848K), 0.0047140 secs]
+[GC 288K->288K(62848K), 0.0002680 secs]
+[Full GC 288K->288K(62848K), 0.0047100 secs]
+[GC 288K->288K(62848K), 0.0002390 secs]
+[Full GC 288K->288K(62848K), 0.0047820 secs]
+[GC 288K->288K(62848K), 0.0002780 secs]
+[Full GC 288K->288K(62848K), 0.0047480 secs]
+[GC 288K->288K(62848K), 0.0002590 secs]
+[Full GC 288K->288K(62848K), 0.0048950 secs]
+[GC 288K->288K(62848K), 0.0004100 secs]
+[Full GC 288K->288K(62848K), 0.0047580 secs]
+[GC 288K->288K(62848K), 0.0003630 secs]
+[Full GC 288K->288K(62848K), 0.0047230 secs]
+[GC 288K->288K(62848K), 0.0003200 secs]
+[Full GC 288K->288K(62848K), 0.0047490 secs]
+[GC 288K->288K(62848K), 0.0002940 secs]
+[Full GC 288K->288K(62848K), 0.0047300 secs]
+[GC 288K->288K(62848K), 0.0016660 secs]
+[Full GC 288K->288K(62848K), 0.0052520 secs]
+[GC 288K->288K(62848K), 0.0003090 secs]
+[Full GC 288K->288K(62848K), 0.0051710 secs]
+[GC 288K->288K(62848K), 0.0003220 secs]
+[Full GC 288K->288K(62848K), 0.0050270 secs]
+[GC 288K->288K(62848K), 0.0003390 secs]
+[Full GC 288K->288K(62848K), 0.0048450 secs]
+[GC 288K->288K(62848K), 0.0003830 secs]
+[Full GC 288K->288K(62848K), 0.0047870 secs]
+[GC 288K->288K(62848K), 0.0003800 secs]
+[Full GC 288K->288K(62848K), 0.0051750 secs]
+[GC 288K->288K(62848K), 0.0004380 secs]
+[Full GC 288K->288K(62848K), 0.0052900 secs]
+[GC 288K->288K(62848K), 0.0003550 secs]
+[Full GC 288K->288K(62848K), 0.0053350 secs]
+[GC 288K->288K(62848K), 0.0003090 secs]
+[Full GC 288K->288K(62848K), 0.0053060 secs]
+[GC 288K->288K(62848K), 0.0003030 secs]
+[Full GC 288K->288K(62848K), 0.0052720 secs]
+[GC 288K->288K(62848K), 0.0003220 secs]
+[Full GC 288K->288K(62848K), 0.0053230 secs]
+[GC 288K->288K(62848K), 0.0003610 secs]
+[Full GC 288K->288K(62848K), 0.0053970 secs]
+[GC 288K->288K(62848K), 0.0003590 secs]
+[Full GC 288K->288K(62848K), 0.0053740 secs]
+[GC 288K->288K(62848K), 0.0003610 secs]
+[Full GC 288K->288K(62848K), 0.0054130 secs]
+[GC 288K->288K(62848K), 0.0004750 secs]
+[Full GC 288K->288K(62848K), 0.0053560 secs]
+[GC 288K->288K(62848K), 0.0003510 secs]
+[Full GC 288K->288K(62848K), 0.0053140 secs]
+[GC 288K->288K(62848K), 0.0004350 secs]
+[Full GC 288K->288K(62848K), 0.0053260 secs]
+[GC 288K->288K(62848K), 0.0004010 secs]
+[Full GC 288K->288K(62848K), 0.0054420 secs]
+[GC 288K->288K(62848K), 0.0004300 secs]
+[Full GC 288K->288K(62848K), 0.0053740 secs]
+[GC 288K->288K(62848K), 0.0003610 secs]
+[Full GC 288K->288K(62848K), 0.0070060 secs]
+[GC 288K->288K(62848K), 0.0003240 secs]
+[Full GC 288K->288K(62848K), 0.0067830 secs]
+[GC 288K->288K(62848K), 0.0003500 secs]
+[Full GC 288K->288K(62848K), 0.0068030 secs]
+[GC 288K->288K(62848K), 0.0003260 secs]
+[Full GC 288K->288K(62848K), 0.0066850 secs]
+[GC 288K->288K(62848K), 0.0004600 secs]
+[Full GC 288K->288K(62848K), 0.0067520 secs]
+[GC 288K->288K(62848K), 0.0003550 secs]
+[Full GC 288K->288K(62848K), 0.0067010 secs]
+[GC 288K->288K(62848K), 0.0004590 secs]
+[Full GC 288K->288K(62848K), 0.0069550 secs]
+[GC 288K->288K(62848K), 0.0004450 secs]
+[Full GC 288K->288K(62848K), 0.0068770 secs]
+[GC 288K->288K(62848K), 0.0005830 secs]
+[Full GC 288K->288K(62848K), 0.0067410 secs]
+[GC 288K->288K(62848K), 0.0004850 secs]
+[Full GC 288K->288K(62848K), 0.0067660 secs]
+[GC 288K->288K(62848K), 0.0004270 secs]
+[Full GC 288K->288K(62848K), 0.0067770 secs]
+[GC 288K->288K(62848K), 0.0004140 secs]
+[Full GC 288K->288K(62848K), 0.0067090 secs]
+[GC 288K->288K(62848K), 0.0004360 secs]
+[Full GC 288K->288K(62848K), 0.0067760 secs]
+[GC 288K->288K(62848K), 0.0005740 secs]
+[Full GC 288K->288K(62848K), 0.0067000 secs]
+[GC 288K->288K(62848K), 0.0004730 secs]
+[Full GC 288K->288K(62848K), 0.0066820 secs]
+[GC 288K->288K(62848K), 0.0003490 secs]
+[Full GC 288K->288K(62848K), 0.0067010 secs]
+[GC 288K->288K(62848K), 0.0004610 secs]
+[Full GC 288K->288K(62848K), 0.0067730 secs]
+[GC 288K->288K(62848K), 0.0005560 secs]
+[Full GC 288K->288K(62848K), 0.0067720 secs]
+[GC 288K->288K(62848K), 0.0004240 secs]
+[Full GC 288K->288K(62848K), 0.0066300 secs]
+[GC 288K->288K(62848K), 0.0005200 secs]
+[Full GC 288K->288K(62848K), 0.0069920 secs]
+[GC 288K->288K(62848K), 0.0004240 secs]
+[Full GC 288K->288K(62848K), 0.0076550 secs]
+[GC 617K->304K(62848K), 0.0004960 secs]
+[Full GC 304K->288K(62848K), 0.0076840 secs]
+[GC 617K->304K(62848K), 0.0003870 secs]
+[Full GC 304K->288K(62848K), 0.0077080 secs]
+[GC 288K->288K(62848K), 0.0004140 secs]
+[Full GC 288K->288K(62848K), 0.0073410 secs]
+[GC 288K->288K(62848K), 0.0004640 secs]
+[Full GC 288K->288K(62848K), 0.0066710 secs]
+[GC 288K->288K(62848K), 0.0004830 secs]
+[Full GC 288K->288K(62848K), 0.0066390 secs]
+[GC 288K->288K(62848K), 0.0006970 secs]
+[Full GC 288K->288K(62848K), 0.0066070 secs]
+[GC 288K->288K(62848K), 0.0003500 secs]
+[Full GC 288K->288K(62848K), 0.0065460 secs]
+[GC 288K->288K(62848K), 0.0004160 secs]
+[Full GC 288K->288K(62848K), 0.0065420 secs]
+[GC 288K->288K(62848K), 0.0003710 secs]
+[Full GC 288K->288K(62848K), 0.0066060 secs]
+[GC 288K->288K(62848K), 0.0003510 secs]
+[Full GC 288K->288K(62848K), 0.0065420 secs]
\ No newline at end of file
diff --git a/caliper/src/test/resources/com/google/caliper/bridge/jdk7-compilation.txt b/caliper/src/test/resources/com/google/caliper/bridge/jdk7-compilation.txt
new file mode 100644
index 0000000..02acb05
--- /dev/null
+++ b/caliper/src/test/resources/com/google/caliper/bridge/jdk7-compilation.txt
@@ -0,0 +1,352 @@
+     91    1    b        java.lang.String::hashCode (67 bytes)
+    106    2    b        sun.nio.cs.UTF_8$Decoder::decode (640 bytes)
+    130    3    b        java.lang.String::lastIndexOf (68 bytes)
+    132    4    b        java.lang.String::indexOf (87 bytes)
+    136    5    b        java.io.UnixFileSystem::normalize (75 bytes)
+    138    1 %  b        java.io.UnixFileSystem::normalize @ 10 (75 bytes)
+    142    6    b        sun.nio.cs.UTF_8$Encoder::encode (361 bytes)
+    149    2 %  b        sun.nio.cs.UTF_8$Encoder::encode @ 20 (361 bytes)
+    162    2             sun.nio.cs.UTF_8$Decoder::decode (640 bytes)   made not entrant
+    165    7    b        sun.nio.cs.UTF_8$Decoder::decode (640 bytes)
+    185    8    b        sun.net.www.ParseUtil::encodePath (336 bytes)
+    200    9    b        java.lang.String::indexOf (166 bytes)
+    236   10    b        java.lang.String::equals (88 bytes)
+    253   11    b        java.util.Arrays::binarySearch0 (72 bytes)
+    255   12    b        com.google.common.base.CharMatcher$10::matches (17 bytes)
+    256   13    b        java.util.Arrays::binarySearch (9 bytes)
+    257    3 %  b        com.google.common.base.CharMatcher::setBits @ 3 (28 bytes)
+made not compilable  com.google.common.base.CharMatcher::matches
+    263   14    b        com.google.common.base.CharMatcher::setBits (28 bytes)
+    363   15    b        java.lang.Object::<init> (1 bytes)
+    365   16    b        java.lang.String::charAt (33 bytes)
+    483   17    b        java.lang.String::length (5 bytes)
+    487   18     n       java.util.zip.ZipFile::getEntryBytes (0 bytes)   (static)
+    524   19    b        java.util.zip.ZipFile::ensureOpen (37 bytes)
+    525   20    b        java.util.zip.ZipFile::access$400 (5 bytes)
+    527   21    b        java.util.zip.ZipFile::access$200 (5 bytes)
+    527   22    b        java.util.zip.ZipFile::access$300 (5 bytes)
+    534   23     n       java.lang.System::arraycopy (0 bytes)   (static)
+    550   24    b        java.lang.Math::min (11 bytes)
+    557   25    b        java.util.HashMap::indexFor (6 bytes)
+    559   26    b        java.util.HashMap::hash (23 bytes)
+    561   27    b        java.lang.String::<init> (20 bytes)
+    563   28    b        java.lang.String::substring (83 bytes)
+    566   29    b        java.util.HashMap::get (79 bytes)
+    569   30    b        java.util.Arrays::copyOfRange (63 bytes)
+    573   31    b        java.lang.String::<init> (72 bytes)
+    577   32    b        java.lang.String::lastIndexOf (12 bytes)
+    588   33     n       java.util.zip.ZipFile::getEntrySize (0 bytes)   (static)
+    588   34    b        java.nio.charset.CharsetDecoder::maxCharsPerByte (5 bytes)
+    589   35     n       java.util.zip.ZipFile::getEntryCSize (0 bytes)   (static)
+    589   36     n       java.util.zip.ZipFile::getEntryMethod (0 bytes)   (static)
+    589   37     n       java.util.zip.ZipFile::freeEntry (0 bytes)   (static)
+    590   38    b        java.util.zip.ZipFile::getZipEntry (245 bytes)
+made not compilable  java.nio.charset.Charset::newDecoder
+    612   39    b        java.util.zip.ZipEntry::<init> (43 bytes)
+    613   40     n       java.util.zip.ZipFile::getEntryFlag (0 bytes)   (static)
+    613   41    b        java.util.zip.ZipCoder::isUTF8 (5 bytes)
+    613   42     n       java.util.zip.ZipFile::getEntryTime (0 bytes)   (static)
+    613   43     n       java.util.zip.ZipFile::getEntryCrc (0 bytes)   (static)
+    613   44    b        java.util.zip.ZipFile::access$1000 (6 bytes)
+    614   45    b        java.util.jar.JarFile$JarFileEntry::<init> (11 bytes)
+    615   46    b        java.util.jar.JarEntry::<init> (6 bytes)
+    615   47    b        java.util.zip.ZipEntry::<init> (115 bytes)
+    616   48    b        sun.misc.URLClassPath::getResourceMapKey (55 bytes)
+    621   49    b        java.util.jar.JarFile$1::hasMoreElements (10 bytes)
+    623   50   !b        java.util.zip.ZipFile$1::hasMoreElements (41 bytes)
+    624   51    b        java.util.zip.ZipEntry::getName (5 bytes)
+    625   52    b        java.util.jar.JarFile$1::nextElement (5 bytes)
+    629   53    b        java.util.jar.JarFile$1::nextElement (26 bytes)
+    633   54    b        java.util.zip.ZipFile$1::nextElement (5 bytes)
+    636   55   !b        java.util.zip.ZipFile$1::nextElement (212 bytes)
+    639   56    b        java.util.zip.ZipFile::access$500 (6 bytes)
+    639   57     n       java.util.zip.ZipFile::getNextEntry (0 bytes)   (static)
+    639   58    b        java.util.zip.ZipFile::access$900 (7 bytes)
+    640   59    b        java.util.ArrayList::size (5 bytes)
+    641   60    b        sun.misc.URLClassPath$JarLoader::addJarEntriesToEntryMap (202 bytes)
+    664   61    b        java.util.ArrayList::get (11 bytes)
+    664   62    b        java.util.ArrayList::rangeCheck (22 bytes)
+    665   63    b        java.util.ArrayList::elementData (7 bytes)
+    707   29             java.util.HashMap::get (79 bytes)   made not entrant
+    719   64    b        java.lang.String::replace (142 bytes)
+    749   65    b        java.lang.String::startsWith (78 bytes)
+    865   66    b        java.util.HashMap::get (79 bytes)
+    867   67    b        java.io.DataOutputStream::writeUTF (435 bytes)
+    913   68    b        java.lang.String::getChars (66 bytes)
+    917   69   !b        sun.reflect.generics.parser.SignatureParser::current (40 bytes)
+    918   70    b        java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes)
+    922   71    b        java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
+    922   72    b        java.io.DataOutputStream::incCount (20 bytes)
+    923   73  s b        java.io.ByteArrayOutputStream::write (32 bytes)
+    933   74    b        java.lang.CharacterData::of (120 bytes)
+    934   75    b        java.lang.CharacterDataLatin1::getProperties (11 bytes)
+    967   76    b        java.util.concurrent.ConcurrentSkipListMap::findPredecessor (121 bytes)
+    979   77    b        java.lang.Integer::compareTo (9 bytes)
+    980   78    b        java.lang.Integer::compareTo (12 bytes)
+    980   79    b        java.lang.Integer::compare (20 bytes)
+   1030   80    b        java.lang.AbstractStringBuilder::append (29 bytes)
+   1046   81    b        java.util.jar.Manifest$FastInputStream::readLine (167 bytes)
+   1073   82    b        net.sf.cglib.asm.ByteVector::putUTF8 (394 bytes)
+   1081   83    b        net.sf.cglib.asm.Type::a (253 bytes)
+   1087   84  s b        java.lang.StringBuffer::append (8 bytes)
+   1091   85    b        net.sf.cglib.asm.Type::a (214 bytes)
+   1116   86    b        java.util.ArrayList::access$100 (5 bytes)
+   1118   87    b        java.lang.AbstractStringBuilder::append (48 bytes)
+   1127   88    b        net.sf.cglib.asm.Type::getArgumentTypes (131 bytes)
+   1154   89    b        sun.misc.MetaIndex::mayContain (51 bytes)
+   1181   90    b        sun.reflect.generics.parser.SignatureParser::advance (37 bytes)
+   1189   91    b        java.lang.Character::isWhitespace (5 bytes)
+   1189   92    b        java.lang.Character::isWhitespace (9 bytes)
+   1190   93    b        java.lang.CharacterDataLatin1::isWhitespace (23 bytes)
+   1190   94    b        sun.reflect.generics.parser.SignatureParser::parseIdentifier (115 bytes)
+   1253   95    b        java.util.Properties$LineReader::readLine (452 bytes)
+made not compilable  java.io.Reader::read
+   1287   96    b        java.util.zip.ZStreamRef::address (5 bytes)
+   1314   97    b        java.lang.StringBuilder::append (8 bytes)
+   1323   98     n       java.lang.Thread::currentThread (0 bytes)   (static)
+   1327   99    b        java.lang.String::compareTo (150 bytes)
+   1346  100    b        sun.misc.URLClassPath::access$100 (5 bytes)
+   1353  101    b        java.util.HashMap::put (126 bytes)
+   1377  102    b        java.lang.AbstractStringBuilder::<init> (12 bytes)
+   1396  103    b        java.util.zip.InflaterInputStream::ensureOpen (18 bytes)
+   1399  104    b        java.util.concurrent.ConcurrentSkipListMap::findNode (108 bytes)
+   1407  105    b        java.util.HashMap::transfer (83 bytes)
+   1413  106    b        sun.reflect.ClassFileAssembler::emitByte (11 bytes)
+   1414  107    b        sun.reflect.ByteVectorImpl::add (38 bytes)
+   1422  108    b        java.util.HashMap$HashIterator::nextEntry (99 bytes)
+   1425  109    b        java.lang.reflect.Method::getName (5 bytes)
+   1428  110     n       java.lang.Object::hashCode (0 bytes)   
+   1443  111    b        java.lang.String::startsWith (7 bytes)
+   1452  112    b        java.lang.ref.Reference::get (5 bytes)
+   1453  113    b        java.lang.Class::searchMethods (90 bytes)
+   1463  114    b        java.util.ArrayList$Itr::hasNext (20 bytes)
+   1463  115    b        java.util.HashMap$HashIterator::<init> (63 bytes)
+   1473  116    b        java.util.ArrayList::ensureCapacityInternal (26 bytes)
+   1478  117    b        java.util.ArrayList::add (29 bytes)
+   1484  118     n       java.lang.Object::clone (0 bytes)   
+   1489  119    b        java.lang.reflect.AccessibleObject::<init> (5 bytes)
+   1490  120    b        sun.reflect.ReflectionFactory::langReflectAccess (15 bytes)
+   1493  121    b        java.lang.Class::copyMethods (36 bytes)
+   1501  122    b        java.util.ArrayList$Itr::checkForComodification (23 bytes)
+   1503  123    b        java.util.Arrays::hashCode (56 bytes)
+   1505  124    b        java.lang.reflect.ReflectAccess::copyMethod (5 bytes)
+   1507  125    b        java.lang.reflect.Method::copy (67 bytes)
+   1508  126    b        java.lang.reflect.Method::<init> (68 bytes)
+   1510  127    b        java.lang.reflect.Method::getDeclaringClass (5 bytes)
+   1521  128    b        java.lang.reflect.Method::getParameterTypes (14 bytes)
+   1524  129   !b        com.sun.jersey.core.reflection.ReflectionHelper::findMethodOnClass (94 bytes)
+   1545  130    b        java.lang.System::getSecurityManager (4 bytes)
+   1546  131    b        java.util.ArrayList$Itr::next (66 bytes)
+   1547  132    b        java.util.ArrayList::access$200 (5 bytes)
+   1553  133    b        java.util.HashMap$HashIterator::hasNext (13 bytes)
+   1555  134    b        java.util.HashMap$Entry::<init> (26 bytes)
+   1558  135    b        java.lang.Integer::valueOf (54 bytes)
+   1560  136    b        java.lang.ref.SoftReference::get (29 bytes)
+   1565  137  s b        java.lang.reflect.Method::declaredAnnotations (39 bytes)
+   1569  138    b        java.lang.StringBuilder::toString (17 bytes)
+   1574  139    b        java.lang.Class::clearCachesOnClassRedefinition (70 bytes)
+   1580  140    b        java.util.AbstractCollection::<init> (5 bytes)
+   1581  141    b        java.lang.Class::checkInitted (19 bytes)
+   1595  142    b        com.sun.jersey.core.reflection.AnnotatedMethod::hasParameterAnnotations (80 bytes)
+   1604  143     n       java.lang.Class::getClassLoader0 (0 bytes)   
+   1607  144    b        java.lang.Class::getMethod0 (97 bytes)
+   1634  145    b        java.util.HashMap$Entry::getKey (5 bytes)
+   1634  146    b        java.util.Collections$EmptyMap::get (2 bytes)
+   1636  147     n       java.lang.String::intern (0 bytes)   
+   1636  148    b        java.lang.reflect.Method::getAnnotation (26 bytes)
+   1640  148             java.lang.reflect.Method::getAnnotation (26 bytes)   made not entrant
+   1641  149    b        java.util.HashMap$KeyIterator::next (8 bytes)
+   1646  150    b        java.lang.reflect.Method::getReturnType (5 bytes)
+   1647  151    b        java.lang.Class::getName (21 bytes)
+   1658  152     n       java.lang.Class::getInterfaces (0 bytes)   
+   1659  153    b        java.util.AbstractList::<init> (10 bytes)
+   1660  154    b        com.sun.jersey.core.reflection.AnnotatedMethod::hasMethodAnnotations (43 bytes)
+   1676  155    b        java.lang.Character::toLowerCase (9 bytes)
+   1677  156    b        java.lang.CharacterDataLatin1::toLowerCase (39 bytes)
+   1688  157    b        java.util.concurrent.locks.AbstractQueuedSynchronizer::getState (5 bytes)
+   1695  158    b        java.lang.Class::argumentTypesToString (78 bytes)
+   1701    2             sun.nio.cs.UTF_8$Decoder::decode (640 bytes)   made zombie
+   1701   29             java.util.HashMap::get (79 bytes)   made zombie
+   1702  159    b        java.lang.Class::checkMemberAccess (78 bytes)
+   1704  160    b        sun.reflect.generics.parser.SignatureParser::parsePackageNameAndSimpleClassTypeSignature (139 bytes)
+   1734  161  s!b        sun.misc.URLClassPath::getLoader (182 bytes)
+   1772  162    b        java.util.Properties::loadConvert (505 bytes)
+   1789  163    b        sun.reflect.ClassFileAssembler::emitConstantPoolUTF8 (50 bytes)
+   1809  164   !b        sun.reflect.UTF8::encode (189 bytes)
+   1818  165    b        sun.reflect.UTF8::utf8Length (81 bytes)
+   1841  166    b        sun.nio.cs.UTF_8$Decoder::decodeArrayLoop (543 bytes)
+   1869  167    b        java.util.regex.Pattern$BmpCharProperty::match (50 bytes)
+made not compilable  java.util.regex.Pattern$CharProperty::isSatisfiedBy
+   1882  168    b        java.util.regex.Matcher::search (109 bytes)
+   2023  169    b        java.lang.Character::charCount (12 bytes)
+   2023  170    b        java.lang.Character::isHighSurrogate (18 bytes)
+   2024  171    b        java.lang.String::codePointAt (44 bytes)
+   2025  172    b        java.lang.Character::codePointAtImpl (41 bytes)
+   2036  173    b        java.nio.Buffer::checkIndex (22 bytes)
+   2036  174    b        java.nio.DirectLongBufferU::ix (10 bytes)
+   2040  175    b        java.util.concurrent.locks.AbstractOwnableSynchronizer::setExclusiveOwnerThread (6 bytes)
+   2041  176    b        sun.misc.URLClassPath::getResource (79 bytes)
+made not compilable  java.net.URLStreamHandler::openConnection
+   2059  176             sun.misc.URLClassPath::getResource (79 bytes)   made not entrant
+   2063  177    b        com.google.common.io.LineBuffer::add (201 bytes)
+made not compilable  com.google.common.io.LineBuffer::handleLine
+   2073  178    b        java.util.regex.Pattern::has (15 bytes)
+   2074  179    b        java.util.ArrayList::<init> (44 bytes)
+   2080  180    b        java.net.URL::getHost (5 bytes)
+   2089  168             java.util.regex.Matcher::search (109 bytes)   made not entrant
+   2089  167             java.util.regex.Pattern$BmpCharProperty::match (50 bytes)   made not entrant
+   2124  181    b        java.util.HashMap::addEntry (58 bytes)
+   2140  182     n       java.lang.System::nanoTime (0 bytes)   (static)
+   2145  183    b        java.util.zip.Inflater::ensureOpen (47 bytes)
+   2204  184    b        java.lang.String::trim (87 bytes)
+   3019  185    b        java.lang.String::indexOf (7 bytes)
+   3023  186    b        java.util.regex.Pattern$Curly::match0 (174 bytes)
+   3030  187    b        java.util.regex.Matcher::reset (83 bytes)
+   3035  188    b        java.util.regex.Pattern$Node::match (27 bytes)
+   3036  148             java.lang.reflect.Method::getAnnotation (26 bytes)   made zombie
+   3037  189    b        java.util.regex.Pattern$BmpCharProperty::match (50 bytes)
+   3039  190    b        java.util.regex.Pattern$Ctype::isSatisfiedBy (24 bytes)
+   3039  191    b        java.util.regex.ASCII::isType (15 bytes)
+   3040  192    b        java.util.regex.ASCII::getType (17 bytes)
+   3042  193    b        java.util.regex.Pattern$CharProperty::match (56 bytes)
+   3045  194    b        java.util.regex.Pattern$Slice::match (79 bytes)
+   3052  195    b        java.util.regex.Matcher::match (109 bytes)
+   3062  196    b        java.util.regex.Pattern$Curly::match (86 bytes)
+   3074  197    b        com.google.common.collect.AbstractIndexedListIterator::hasNext (17 bytes)
+   3078  198    b        java.util.concurrent.ConcurrentHashMap$Segment::rehash (262 bytes)
+   3094  101             java.util.HashMap::put (126 bytes)   made not entrant
+  15172  199    b        java.util.concurrent.locks.AbstractOwnableSynchronizer::getExclusiveOwnerThread (5 bytes)
+  17427  200     n       sun.misc.Unsafe::compareAndSwapInt (0 bytes)   
+  17431  193             java.util.regex.Pattern$CharProperty::match (56 bytes)   made not entrant
+  17436  201    b        java.lang.String::toUpperCase (442 bytes)
+  17484  202   !b        sun.misc.URLClassPath$JarLoader::getResource (91 bytes)
+  17518  203    b        java.nio.Buffer::position (43 bytes)
+  17519  167             java.util.regex.Pattern$BmpCharProperty::match (50 bytes)   made zombie
+  17519  176             sun.misc.URLClassPath::getResource (79 bytes)   made zombie
+  17519  168             java.util.regex.Matcher::search (109 bytes)   made zombie
+  17519  204    b        java.nio.ByteBuffer::arrayOffset (35 bytes)
+  17520  205    b        java.nio.CharBuffer::arrayOffset (35 bytes)
+  17524  206    b        java.nio.Buffer::position (5 bytes)
+  17524  207    b        sun.nio.cs.UTF_8$Encoder::encodeArrayLoop (489 bytes)
+  17531  208    b        java.nio.charset.CoderResult::isUnderflow (13 bytes)
+  17539  209    b        java.nio.Buffer::limit (62 bytes)
+  17540  210    b        java.nio.Buffer::<init> (121 bytes)
+  17541  211    b        java.nio.Buffer::remaining (10 bytes)
+  17542  212    b        java.nio.charset.CoderResult::isOverflow (14 bytes)
+  17542  213    b        java.nio.Buffer::hasRemaining (17 bytes)
+  17542  214    b        java.nio.CharBuffer::hasArray (20 bytes)
+  17543  215    b        java.nio.ByteBuffer::hasArray (20 bytes)
+  17543  216   !b        java.nio.CharBuffer::wrap (20 bytes)
+  17545  217    b        java.nio.HeapCharBuffer::<init> (14 bytes)
+  17546  218    b        java.nio.CharBuffer::<init> (22 bytes)
+  17547  219    b        sun.nio.cs.StreamEncoder::ensureOpen (18 bytes)
+  17547  220    b        sun.nio.cs.StreamEncoder::implWrite (156 bytes)
+  17556  221   !b        java.nio.charset.CharsetEncoder::encode (285 bytes)
+  17561  222    b        sun.nio.cs.UTF_8$Encoder::encodeLoop (28 bytes)
+  17562  223   !b        sun.nio.cs.StreamEncoder::write (78 bytes)
+  17565  224    b        com.google.gson.stream.JsonWriter::string (365 bytes)
+  17577  225    b        sun.nio.cs.StreamEncoder::write (37 bytes)
+  17588  224             com.google.gson.stream.JsonWriter::string (365 bytes)   made not entrant
+  17589  226    b        com.google.gson.stream.JsonWriter::string (365 bytes)
+  17618  227    b        java.lang.String::toLowerCase (477 bytes)
+  17637   10             java.lang.String::equals (88 bytes)   made not entrant
+  17641   73  s          java.io.ByteArrayOutputStream::write (32 bytes)   made not entrant
+  17656    7             sun.nio.cs.UTF_8$Decoder::decode (640 bytes)   made not entrant
+  17656   34             java.nio.charset.CharsetDecoder::maxCharsPerByte (5 bytes)   made not entrant
+  17656   38             java.util.zip.ZipFile::getZipEntry (245 bytes)   made not entrant
+  17658  228    b        sun.nio.cs.UTF_8$Decoder::decode (640 bytes)
+  17672  229    b        java.lang.StringBuilder::<init> (7 bytes)
+  17680  230    b        java.math.BigInteger::destructiveMulAdd (150 bytes)
+  17685  101             java.util.HashMap::put (126 bytes)   made zombie
+  17685  231    b        java.util.regex.Pattern$Single::isSatisfiedBy (14 bytes)
+  17687  232    b        java.lang.Integer::parseInt (261 bytes)
+  17693  233    b        java.lang.Character::digit (6 bytes)
+  17693  234    b        java.lang.Character::digit (10 bytes)
+  17694  235    b        java.lang.CharacterDataLatin1::digit (91 bytes)
+  17698  236    b        sun.security.util.Cache$EqualByteArray::hashCode (57 bytes)
+  17721  237    b        java.lang.String::equals (88 bytes)
+  17733  238    b        java.math.BigInteger::stripLeadingZeroBytes (132 bytes)
+  17740  239  s b        java.io.ByteArrayInputStream::read (36 bytes)
+  17749  240  s b        java.io.ByteArrayInputStream::available (10 bytes)
+  17756  241    b        java.io.ByteArrayInputStream::mark (9 bytes)
+  17768  242    b        sun.security.util.DerInputStream::getLength (111 bytes)
+made not compilable  java.io.InputStream::read
+  17774  243    b        java.util.Arrays::copyOf (19 bytes)
+  17781  244    b        java.io.DataInputStream::readUTF (501 bytes)
+  17822  245    b        sun.security.util.ObjectIdentifier::check (78 bytes)
+  17829  246  s b        java.io.ByteArrayOutputStream::write (32 bytes)
+  17854  247    b        sun.security.util.DerInputStream::<init> (19 bytes)
+  17858  248    b        sun.security.util.DerInputStream::available (8 bytes)
+  17863  249   !b        java.security.cert.Certificate::hashCode (34 bytes)
+  17889  202   !         sun.misc.URLClassPath$JarLoader::getResource (91 bytes)   made not entrant
+  17919  250    b        java.util.Arrays::equals (54 bytes)
+  17930  251    b        java.util.Arrays::hashCode (44 bytes)
+  17936  252    b        java.math.BigInteger::mulAdd (81 bytes)
+  17957  195             java.util.regex.Matcher::match (109 bytes)   made not entrant
+  17965    4 %  b        com.sun.crypto.provider.AESCrypt::<clinit> @ 724 (1577 bytes)
+  18073  232             java.lang.Integer::parseInt (261 bytes)   made not entrant
+  18142  233             java.lang.Character::digit (6 bytes)   made not entrant
+  18142   74             java.lang.CharacterData::of (120 bytes)   made not entrant
+  18142   91             java.lang.Character::isWhitespace (5 bytes)   made not entrant
+  18142   94             sun.reflect.generics.parser.SignatureParser::parseIdentifier (115 bytes)   made not entrant
+  18142   92             java.lang.Character::isWhitespace (9 bytes)   made not entrant
+  18142  155             java.lang.Character::toLowerCase (9 bytes)   made not entrant
+  18142  201             java.lang.String::toUpperCase (442 bytes)   made not entrant
+  18142  234             java.lang.Character::digit (10 bytes)   made not entrant
+  18142  227             java.lang.String::toLowerCase (477 bytes)   made not entrant
+  18143  253    b        java.lang.Character::toUpperCaseEx (30 bytes)
+  18145   10             java.lang.String::equals (88 bytes)   made zombie
+  18148  254    b        java.io.BufferedInputStream::getBufIfOpen (21 bytes)
+  18148  255  s b        java.io.BufferedInputStream::read (49 bytes)
+  18151  256    b        java.io.DataInputStream::readChar (40 bytes)
+  18158  193             java.util.regex.Pattern$CharProperty::match (56 bytes)   made zombie
+  18158  224             com.google.gson.stream.JsonWriter::string (365 bytes)   made zombie
+  18158    5 %  b        sun.text.normalizer.NormalizerDataReader::read @ 11 (83 bytes)
+  18165  256             java.io.DataInputStream::readChar (40 bytes)   made not entrant
+  18169  257    b        java.io.DataInputStream::readInt (72 bytes)
+  18180  258    b        java.io.DataInputStream::readChar (40 bytes)
+  18192  259    b        java.lang.StringBuilder::append (8 bytes)
+  18203  260    b        java.lang.CharacterData::of (120 bytes)
+  18206  261    b        java.lang.Character::toLowerCase (6 bytes)
+made not compilable  java.lang.CharacterData::toLowerCase
+  18208  262    b        java.lang.String::toLowerCase (477 bytes)
+  18229  263    b        java.lang.String::toUpperCase (442 bytes)
+  18251  264    b        java.lang.Character::isWhitespace (5 bytes)
+made not compilable  java.lang.CharacterData::isWhitespace
+  18251  265    b        java.lang.Character::isWhitespace (9 bytes)
+  18255  266    b        sun.text.normalizer.NormalizerBase::normalize (223 bytes)
+  18262  267   !b        sun.security.x509.AVA::toRFC2253CanonicalString (484 bytes)
+made not compilable  java.nio.charset.spi.CharsetProvider::charsetForName
+  18334  268    b        java.lang.String::regionMatches (157 bytes)
+made not compilable  java.lang.CharacterData::toUpperCase
+  18347    7             sun.nio.cs.UTF_8$Decoder::decode (640 bytes)   made zombie
+  18347   34             java.nio.charset.CharsetDecoder::maxCharsPerByte (5 bytes)   made zombie
+  18347   73  s          java.io.ByteArrayOutputStream::write (32 bytes)   made zombie
+  18347   38             java.util.zip.ZipFile::getZipEntry (245 bytes)   made zombie
+  18348  232             java.lang.Integer::parseInt (261 bytes)   made zombie
+  18348  249   !         java.security.cert.Certificate::hashCode (34 bytes)   made not entrant
+  18357  269    b        sun.security.provider.SHA::implCompress (491 bytes)
+  18429    6 %  b        com.sun.crypto.provider.ARCFOURCipher::crypt @ 15 (129 bytes)
+  18435  270     n       sun.misc.Unsafe::getInt (0 bytes)   
+  18436  271    b        java.lang.Integer::reverseBytes (26 bytes)
+  18436  272    b        com.sun.crypto.provider.ARCFOURCipher::crypt (129 bytes)
+  18442  202   !         sun.misc.URLClassPath$JarLoader::getResource (91 bytes)   made zombie
+  18442  195             java.util.regex.Matcher::match (109 bytes)   made zombie
+  49381  233             java.lang.Character::digit (6 bytes)   made zombie
+  49381   74             java.lang.CharacterData::of (120 bytes)   made zombie
+  49381   91             java.lang.Character::isWhitespace (5 bytes)   made zombie
+  49381   94             sun.reflect.generics.parser.SignatureParser::parseIdentifier (115 bytes)   made zombie
+  49381   92             java.lang.Character::isWhitespace (9 bytes)   made zombie
+  49381  155             java.lang.Character::toLowerCase (9 bytes)   made zombie
+  49381  161  s!         sun.misc.URLClassPath::getLoader (182 bytes)   made not entrant
+  49381  201             java.lang.String::toUpperCase (442 bytes)   made zombie
+  49381  234             java.lang.Character::digit (10 bytes)   made zombie
+  49381  227             java.lang.String::toLowerCase (477 bytes)   made zombie
+  49382  273    b        sun.misc.URLClassPath$LoaderSearchCursor::nextLoader (211 bytes)
+  49406   66             java.util.HashMap::get (79 bytes)   made not entrant
+  49420  274    b        java.util.HashMap$EntryIterator::next (5 bytes)
+  49422  275    b        java.util.HashMap$EntryIterator::next (5 bytes)
+  49434  276    b        java.util.HashMap$Entry::hashCode (38 bytes)
+  49436  256             java.io.DataInputStream::readChar (40 bytes)   made zombie
+  49436  277    b        java.util.AbstractMap::hashCode (43 bytes)
+made not compilable  java.util.AbstractMap::entrySet
\ No newline at end of file
diff --git a/caliper/src/test/resources/com/google/caliper/bridge/jdk7-flags.txt b/caliper/src/test/resources/com/google/caliper/bridge/jdk7-flags.txt
new file mode 100644
index 0000000..a09cf57
--- /dev/null
+++ b/caliper/src/test/resources/com/google/caliper/bridge/jdk7-flags.txt
@@ -0,0 +1,832 @@
+     bool AHRByDeathCollectTimeRatio                = false           {product}           
+     bool AHRByMinorPauseTimeMajorFreq              = false           {product}           
+     bool AHRByPromoToAllocRatio                    = false           {product}           
+     bool AHRBySurvivorAge                          = false           {product}           
+    uintx AHRIncrementSize                          = 5242880         {product}           
+    uintx AHRMaxDeathCollectTimeRatio               = 55              {product}           
+    uintx AHRMaxMinorInvocationsPerMajor            = 30              {product}           
+    uintx AHRMaxMinorPauseTimeMillis                = 100             {product}           
+    uintx AHRMaxPromoToAllocRatio                   = 25              {product}           
+    uintx AHRMaxRatio                               = 100             {product}           
+    uintx AHRMaxSize                                = 104857600       {product}           
+    uintx AHRMaxSurvivorAge                         = 10              {product}           
+    uintx AHRMinDeathCollectTimeRatio               = 50              {product}           
+    uintx AHRMinMinorInvocationsPerMajor            = 5               {product}           
+    uintx AHRMinMinorPauseTimeMillis                = 150             {product}           
+    uintx AHRMinPromoToAllocRatio                   = 2               {product}           
+    uintx AHRMinSurvivorAge                         = 2               {product}           
+     bool AdaptiveHeapRebalance                     = false           {product}           
+    uintx AdaptivePermSizeWeight                    = 20              {product}           
+    uintx AdaptiveSizeDecrementScaleFactor          = 4               {product}           
+    uintx AdaptiveSizeMajorGCDecayTimeScale         = 10              {product}           
+    uintx AdaptiveSizePausePolicy                   = 0               {product}           
+    uintx AdaptiveSizePolicyCollectionCostMargin    = 50              {product}           
+    uintx AdaptiveSizePolicyInitializingSteps       = 20              {product}           
+    uintx AdaptiveSizePolicyOutputInterval          = 0               {product}           
+    uintx AdaptiveSizePolicyWeight                  = 10              {product}           
+    uintx AdaptiveSizeThroughPutPolicy              = 0               {product}           
+    uintx AdaptiveTimeWeight                        = 25              {product}           
+     bool AdjustConcurrency                         = false           {product}           
+     bool AggressiveOpts                            = false           {product}           
+     intx AliasLevel                                = 3               {product}           
+     intx AllocateInstancePrefetchLines             = 1               {product}           
+     intx AllocatePrefetchDistance                  = 192             {product}           
+     intx AllocatePrefetchInstr                     = 0               {product}           
+     intx AllocatePrefetchLines                     = 4               {product}           
+     intx AllocatePrefetchStepSize                  = 64              {product}           
+     intx AllocatePrefetchStyle                     = 1               {product}           
+     bool AllowJNIEnvProxy                          = false           {product}           
+     bool AllowParallelDefineClass                  = false           {product}           
+     bool AllowUserSignalHandlers                   = false           {product}           
+     bool AlwaysActAsServerClassMachine             = false           {product}           
+     bool AlwaysCompileLoopMethods                  = false           {product}           
+     intx AlwaysInflate                             = 0               {product}           
+     bool AlwaysLockClassLoader                     = false           {product}           
+     bool AlwaysPreTouch                            = false           {product}           
+     bool AlwaysRestoreFPU                          = false           {product}           
+     bool AlwaysTenure                              = false           {product}           
+     bool AnonymousClasses                          = false           {product}           
+    uintx ArraycopyDstPrefetchDistance              = 0               {product}           
+    uintx ArraycopySrcPrefetchDistance              = 0               {product}           
+     bool AssertOnSuspendWaitFailure                = false           {product}           
+     intx Atomics                                   = 0               {product}           
+     intx AutoBoxCacheMax                           = 128             {C2 product}        
+    uintx AutoGCSelectPauseMillis                   = 5000            {product}           
+     intx BCEATraceLevel                            = 0               {product}           
+     intx BackEdgeThreshold                         = 100000          {pd product}        
+     bool BackgroundCompilation                     = true            {pd product}        
+    uintx BaseFootPrintEstimate                     = 268435456       {product}           
+     intx BiasedLockingBulkRebiasThreshold          = 20              {product}           
+     intx BiasedLockingBulkRevokeThreshold          = 40              {product}           
+     intx BiasedLockingDecayTime                    = 25000           {product}           
+     intx BiasedLockingStartupDelay                 = 4000            {product}           
+     bool BindCMSThreadToCPU                        = false           {diagnostic}        
+     bool BindGCTaskThreadsToCPUs                   = false           {product}           
+     intx BlockCopyLowLimit                         = 2048            {product}           
+     bool BlockLayoutByFrequency                    = true            {C2 product}        
+     intx BlockLayoutMinDiamondPercentage           = 20              {C2 product}        
+     bool BlockLayoutRotateLoops                    = true            {C2 product}        
+     bool BlockOffsetArrayUseUnallocatedBlock       = false           {diagnostic}        
+     intx BlockZeroingLowLimit                      = 2048            {product}           
+     bool BranchOnRegister                          = false           {C2 product}        
+     bool BytecodeVerificationLocal                 = false           {product}           
+     bool BytecodeVerificationRemote                = true            {product}           
+     bool C1OptimizeVirtualCallProfiling            = true            {C1 product}        
+     bool C1ProfileBranches                         = true            {C1 product}        
+     bool C1ProfileCalls                            = true            {C1 product}        
+     bool C1ProfileCheckcasts                       = true            {C1 product}        
+     bool C1ProfileInlinedCalls                     = true            {C1 product}        
+     bool C1ProfileVirtualCalls                     = true            {C1 product}        
+     bool C1UpdateMethodData                        = true            {C1 product}        
+     intx CICompilerCount                           = 2               {product}           
+     bool CICompilerCountPerCPU                     = false           {product}           
+     bool CITime                                    = false           {product}           
+     bool CMSAbortSemantics                         = false           {product}           
+    uintx CMSAbortablePrecleanMinWorkPerIteration   = 100             {product}           
+     intx CMSAbortablePrecleanWaitMillis            = 100             {manageable}        
+    uintx CMSBitMapYieldQuantum                     = 10485760        {product}           
+    uintx CMSBootstrapOccupancy                     = 50              {product}           
+     bool CMSClassUnloadingEnabled                  = false           {product}           
+    uintx CMSClassUnloadingMaxInterval              = 0               {product}           
+     bool CMSCleanOnEnter                           = true            {product}           
+     bool CMSCompactWhenClearAllSoftRefs            = true            {product}           
+    uintx CMSConcMarkMultiple                       = 32              {product}           
+     bool CMSConcurrentMTEnabled                    = true            {product}           
+    uintx CMSCoordinatorYieldSleepCount             = 10              {product}           
+     bool CMSDumpAtPromotionFailure                 = false           {product}           
+     bool CMSEdenChunksRecordAlways                 = false           {product}           
+    uintx CMSExpAvgFactor                           = 50              {product}           
+     bool CMSExtrapolateSweep                       = false           {product}           
+    uintx CMSFullGCsBeforeCompaction                = 0               {product}           
+    uintx CMSIncrementalDutyCycle                   = 10              {product}           
+    uintx CMSIncrementalDutyCycleMin                = 0               {product}           
+     bool CMSIncrementalMode                        = false           {product}           
+    uintx CMSIncrementalOffset                      = 0               {product}           
+     bool CMSIncrementalPacing                      = true            {product}           
+    uintx CMSIncrementalSafetyFactor                = 10              {product}           
+    uintx CMSIndexedFreeListReplenish               = 4               {product}           
+     intx CMSInitiatingOccupancyFraction            = -1              {product}           
+     intx CMSInitiatingOccupancyFractionDelta       = -1              {product}           
+     intx CMSInitiatingPermOccupancyFraction        = -1              {product}           
+     intx CMSInitiatingPermOccupancyFractionDelta   = -1              {product}           
+     intx CMSInitiatingRamLimitFraction             = -1              {product}           
+     intx CMSIsTooFullPercentage                    = 98              {product}           
+   double CMSLargeCoalSurplusPercent                = 0.950000        {product}           
+   double CMSLargeSplitSurplusPercent               = 1.000000        {product}           
+     bool CMSLoopWarn                               = false           {product}           
+    uintx CMSMaxAbortablePrecleanLoops              = 0               {product}           
+     intx CMSMaxAbortablePrecleanTime               = 5000            {product}           
+    uintx CMSOldPLABMax                             = 1024            {product}           
+    uintx CMSOldPLABMin                             = 16              {product}           
+    uintx CMSOldPLABNumRefills                      = 4               {product}           
+    uintx CMSOldPLABReactivityCeiling               = 10              {product}           
+    uintx CMSOldPLABReactivityFactor                = 2               {product}           
+     bool CMSOldPLABResizeQuicker                   = false           {product}           
+    uintx CMSOldPLABToleranceFactor                 = 4               {product}           
+     bool CMSPLABRecordAlways                       = true            {product}           
+    uintx CMSParPromoteBlocksToClaim                = 16              {product}           
+     bool CMSParallelInitialMarkEnabled             = false           {product}           
+     bool CMSParallelRemarkEnabled                  = true            {product}           
+    uintx CMSParallelSTWFullGCHeapRegionSize        = 1048576         {product}           
+     bool CMSParallelSurvivorRemarkEnabled          = true            {product}           
+     bool CMSPermGenPrecleaningEnabled              = true            {product}           
+    uintx CMSPrecleanDenominator                    = 3               {product}           
+    uintx CMSPrecleanIter                           = 3               {product}           
+    uintx CMSPrecleanNumerator                      = 2               {product}           
+     bool CMSPrecleanRefLists1                      = true            {product}           
+     bool CMSPrecleanRefLists2                      = false           {product}           
+     bool CMSPrecleanSurvivors1                     = false           {product}           
+     bool CMSPrecleanSurvivors2                     = true            {product}           
+    uintx CMSPrecleanThreshold                      = 1000            {product}           
+     bool CMSPrecleaningEnabled                     = true            {product}           
+     bool CMSPrintChunksInDump                      = false           {product}           
+     bool CMSPrintEdenSurvivorChunks                = false           {product}           
+     bool CMSPrintObjectsInDump                     = false           {product}           
+    uintx CMSRemarkVerifyVariant                    = 1               {product}           
+     bool CMSReplenishIntermediate                  = true            {product}           
+    uintx CMSRescanMultiple                         = 32              {product}           
+    uintx CMSRevisitStackSize                       = 1048576         {product}           
+    uintx CMSSamplingGrain                          = 16384           {product}           
+     bool CMSScavengeBeforeRemark                   = false           {product}           
+    uintx CMSScheduleRemarkEdenPenetration          = 50              {product}           
+    uintx CMSScheduleRemarkEdenSizeThreshold        = 2097152         {product}           
+    uintx CMSScheduleRemarkSamplingRatio            = 5               {product}           
+   double CMSSmallCoalSurplusPercent                = 1.050000        {product}           
+   double CMSSmallSplitSurplusPercent               = 1.100000        {product}           
+     bool CMSSplitIndexedFreeListBlocks             = true            {product}           
+     intx CMSTriggerPermRatio                       = 80              {product}           
+     intx CMSTriggerRatio                           = 80              {product}           
+     bool CMSUseGCOverheadLimit                     = true            {product}           
+     intx CMSWaitDuration                           = 2000            {manageable}        
+    uintx CMSWorkQueueDrainThreshold                = 10              {product}           
+     bool CMSYield                                  = true            {product}           
+    uintx CMSYieldSleepCount                        = 0               {product}           
+     intx CMSYoungGenPerWorker                      = 67108864        {pd product}        
+    uintx CMS_FLSPadding                            = 1               {product}           
+    uintx CMS_FLSWeight                             = 75              {product}           
+    uintx CMS_SweepPadding                          = 1               {product}           
+    uintx CMS_SweepTimerThresholdMillis             = 10              {product}           
+    uintx CMS_SweepWeight                           = 75              {product}           
+    uintx CPUForCMSThread                           = 0               {diagnostic}        
+     bool CheckJNICalls                             = false           {product}           
+     bool ClassUnloading                            = true            {product}           
+     intx ClearFPUAtPark                            = 0               {product}           
+     bool ClipInlining                              = true            {product}           
+    uintx CodeCacheExpansionSize                    = 32768           {pd product}        
+    uintx CodeCacheFlushingMinimumFreeSpace         = 1536000         {product}           
+    uintx CodeCacheMinimumFreeSpace                 = 512000          {product}           
+     bool CollectGen0First                          = false           {product}           
+     bool CompactFields                             = true            {product}           
+     intx CompilationPolicyChoice                   = 0               {product}           
+     intx CompilationRepeat                         = 0               {C1 product}        
+ccstrlist CompileCommand                            =                 {product}           
+    ccstr CompileCommandFile                        =                 {product}           
+ccstrlist CompileOnly                               =                 {product}           
+     intx CompileThreshold                          = 10000           {pd product}        
+     bool CompilerThreadHintNoPreempt               = true            {product}           
+     intx CompilerThreadPriority                    = -1              {product}           
+     intx CompilerThreadStackSize                   = 0               {pd product}        
+    uintx ConcGCThreads                             = 0               {product}           
+     intx ConditionalMoveLimit                      = 3               {C2 pd product}     
+     bool ConvertSleepToYield                       = true            {pd product}        
+     bool ConvertYieldToSleep                       = false           {product}           
+     bool CreateMinidumpOnCrash                     = false           {product}           
+     bool CycleTime                                 = false           {product}           
+     bool DTraceAllocProbes                         = false           {product}           
+     bool DTraceMethodProbes                        = false           {product}           
+     bool DTraceMonitorProbes                       = false           {product}           
+     bool DeallocateHeapPages                       = true            {product}           
+     bool DeallocateStackPages                      = true            {product}           
+    uintx DeallocateStackPagesMinIntervalMs         = 100             {product}           
+     bool DebugContinuation                         = false           {diagnostic}        
+     bool DebugInlinedCalls                         = true            {diagnostic}        
+     bool DebugNonSafepoints                        = false           {diagnostic}        
+    uintx DefaultMaxRAMFraction                     = 4               {product}           
+     intx DefaultThreadPriority                     = -1              {product}           
+     bool DeferInitialCardMark                      = false           {diagnostic}        
+     intx DeferPollingPageLoopCount                 = -1              {product}           
+     intx DeferThrSuspendLoopCount                  = 4000            {product}           
+     bool DeoptimizeRandom                          = false           {product}           
+     bool DisableAttachMechanism                    = false           {product}           
+     bool DisableExplicitGC                         = false           {product}           
+ccstrlist DisableIntrinsic                          =                 {diagnostic}        
+     bool DisplayVMOutput                           = true            {diagnostic}        
+     bool DisplayVMOutputToStderr                   = false           {product}           
+     bool DisplayVMOutputToStdout                   = false           {product}           
+     bool DoEscapeAnalysis                          = true            {C2 product}        
+     intx DominatorSearchLimit                      = 1000            {C2 diagnostic}     
+     bool DontCompileHugeMethods                    = true            {product}           
+     bool DontYieldALot                             = false           {pd product}        
+     bool DumpSharedSpaces                          = false           {product}           
+     bool EagerXrunInit                             = false           {product}           
+     intx EliminateAllocationArraySizeLimit         = 64              {C2 product}        
+     bool EliminateAllocations                      = true            {C2 product}        
+     bool EliminateAutoBox                          = false           {C2 diagnostic}     
+     bool EliminateLocks                            = true            {C2 product}        
+     intx EmitSync                                  = 0               {product}           
+     bool EnableInvokeDynamic                       = true            {diagnostic}        
+    uintx ErgoHeapSizeLimit                         = 0               {product}           
+    ccstr ErrorFile                                 =                 {product}           
+    ccstr ErrorReportServer                         =                 {product}           
+     bool EstimateArgEscape                         = true            {product}           
+     intx EventLogLength                            = 2000            {product}           
+     bool ExplicitGCInvokesConcurrent               = false           {product}           
+     bool ExplicitGCInvokesConcurrentAndUnloadsClasses  = false           {product}           
+     bool ExtendedDTraceProbes                      = false           {product}           
+     bool FLSAlwaysCoalesceLarge                    = false           {product}           
+    uintx FLSCoalescePolicy                         = 2               {product}           
+   double FLSLargestBlockCoalesceProximity          = 0.990000        {product}           
+     bool FLSVerifyAllHeapReferences                = false           {diagnostic}        
+     bool FLSVerifyIndexTable                       = false           {diagnostic}        
+     bool FLSVerifyLists                            = false           {diagnostic}        
+     bool FailOverToOldVerifier                     = true            {product}           
+     bool FastCardTableScan                         = true            {product}           
+     bool FastTLABRefill                            = true            {product}           
+     intx FenceInstruction                          = 0               {product}           
+     intx FieldsAllocationStyle                     = 1               {product}           
+     bool FilterSpuriousWakeups                     = true            {product}           
+     bool ForceNUMA                                 = false           {product}           
+     bool ForceTimeHighResolution                   = false           {product}           
+     intx FreqInlineSize                            = 325             {pd product}        
+     bool FullProfileOnReInterpret                  = true            {diagnostic}        
+   double G1ConcMarkStepDurationMillis              = 10.000000       {product}           
+     intx G1ConcRefinementGreenZone                 = 0               {product}           
+     intx G1ConcRefinementRedZone                   = 0               {product}           
+     intx G1ConcRefinementServiceIntervalMillis     = 300             {product}           
+    uintx G1ConcRefinementThreads                   = 0               {product}           
+     intx G1ConcRefinementThresholdStep             = 0               {product}           
+     intx G1ConcRefinementYellowZone                = 0               {product}           
+     intx G1ConfidencePercent                       = 50              {product}           
+    uintx G1HeapRegionSize                          = 0               {product}           
+     intx G1MarkRegionStackSize                     = 1048576         {product}           
+     bool G1PrintHeapRegions                        = false           {diagnostic}        
+     bool G1PrintRegionLivenessInfo                 = false           {diagnostic}        
+     intx G1RSetRegionEntries                       = 0               {product}           
+    uintx G1RSetScanBlockSize                       = 64              {product}           
+     intx G1RSetSparseRegionEntries                 = 0               {product}           
+     intx G1RSetUpdatingPauseTimePercent            = 10              {product}           
+     intx G1RefProcDrainInterval                    = 10              {product}           
+    uintx G1ReservePercent                          = 10              {product}           
+    uintx G1SATBBufferEnqueueingThresholdPercent    = 60              {product}           
+     intx G1SATBBufferSize                          = 1024            {product}           
+     bool G1SummarizeConcMark                       = false           {diagnostic}        
+     bool G1SummarizeRSetStats                      = false           {diagnostic}        
+     intx G1SummarizeRSetStatsPeriod                = 0               {diagnostic}        
+     bool G1TraceConcRefinement                     = false           {diagnostic}        
+     intx G1UpdateBufferSize                        = 256             {product}           
+     bool G1UseAdaptiveConcRefinement               = true            {product}           
+    uintx GCDrainStackTargetSize                    = 64              {product}           
+    uintx GCHeapFreeLimit                           = 2               {product}           
+    uintx GCLockerEdenExpansionPercent              = 5               {product}           
+     bool GCLockerInvokesConcurrent                 = false           {product}           
+    uintx GCLogFileSize                             = 0               {product}           
+     bool GCOverheadReporting                       = false           {product}           
+     intx GCOverheadReportingPeriodMS               = 100             {product}           
+     bool GCParallelVerificationEnabled             = true            {diagnostic}        
+    uintx GCPauseIntervalMillis                     = 0               {product}           
+    uintx GCTaskTimeStampEntries                    = 200             {product}           
+    uintx GCTimeLimit                               = 98              {product}           
+    uintx GCTimeRatio                               = 99              {product}           
+     bool GoogleAdjustGCThreads                     = false           {product}           
+     bool GoogleAgent                               = true            {product}           
+    ccstr GoogleAgentFlags                          =                 {product}           
+     bool GoogleAgentWS                             = false           {product}           
+    ccstr GoogleAgentWSFlags                        =                 {product}           
+     bool GoogleAlignInterpreterCalls               = true            {product}           
+     bool GoogleDetailOmittedStackTrace             = true            {product}           
+    uintx GoogleGCHeapFreeLimitPolicy               = 2               {product}           
+     bool GoogleGetMethodsInOrder                   = false           {diagnostic}        
+     bool GoogleGetMethodsReverse                   = false           {diagnostic}        
+     bool GoogleHeapInstrumentation                 = false           {product}           
+     bool GoogleHeapMonitor                         = true            {product}           
+     bool GoogleHeapSamplingInstrumentation         = false           {product}           
+     bool GoogleInheritAltSigStack                  = false           {product}           
+    uintx GoogleLogRotationSize                     = 0               {diagnostic}        
+    uintx GoogleMaxGarbageHeapzTraces               = 200             {product}           
+     bool GoogleScoping                             = false           {product}           
+    uintx GoogleSoftRefLRUPolicy                    = 0               {product}           
+    uintx GoogleSoftRefLRUPolicyExcludedMB          = 0               {product}           
+    uintx GoogleUseConstantOMProvision              = 0               {product}           
+     bool GoogleUseLibunwind                        = false           {pd product}        
+     bool GoogleUseSSEForVM                         = false           {product}           
+     intx GuaranteedSafepointInterval               = 1000            {diagnostic}        
+    ccstr HPILibPath                                =                 {product}           
+    uintx HeapBaseMinAddress                        = 2147483648      {pd product}        
+     bool HeapDumpAfterFullGC                       = false           {manageable}        
+     bool HeapDumpBeforeFullGC                      = false           {manageable}        
+     bool HeapDumpOnOutOfMemoryError                = false           {manageable}        
+    ccstr HeapDumpPath                              =                 {manageable}        
+    uintx HeapFirstMaximumCompactionCount           = 3               {product}           
+    uintx HeapMaximumCompactionInterval             = 20              {product}           
+     bool HoistFinalLoads                           = false           {product}           
+     bool IgnoreUnrecognizedVMOptions               = false           {product}           
+    uintx InitialCodeCacheSize                      = 2359296         {pd product}        
+     bool InitialCompileFast                        = false           {diagnostic}        
+     bool InitialCompileReallyFast                  = false           {diagnostic}        
+    uintx InitialHeapSize                          := 67108864        {product}           
+    uintx InitialRAMFraction                        = 64              {product}           
+    uintx InitialSurvivorRatio                      = 8               {product}           
+     intx InitialTenuringThreshold                  = 7               {product}           
+    uintx InitiatingHeapOccupancyPercent            = 45              {product}           
+     bool Inline                                    = true            {product}           
+     bool InlineGetCurrentThreadAllocatedMemory     = false           {product}           
+     bool InlineMethodsWithNullUnloadedTypesInSignature  = true            {product}           
+     intx InlineSmallCode                           = 1000            {pd product}        
+     bool InsertMemBarAfterArraycopy                = true            {C2 product}        
+     intx InteriorEntryAlignment                    = 4               {C2 pd product}     
+     intx InterpreterProfilePercentage              = 33              {product}           
+     bool JNIDetachReleasesMonitors                 = true            {product}           
+     bool JavaMonitorsInStackTrace                  = true            {product}           
+     intx JavaPriority10_To_OSPriority              = -1              {product}           
+     intx JavaPriority1_To_OSPriority               = -1              {product}           
+     intx JavaPriority2_To_OSPriority               = -1              {product}           
+     intx JavaPriority3_To_OSPriority               = -1              {product}           
+     intx JavaPriority4_To_OSPriority               = -1              {product}           
+     intx JavaPriority5_To_OSPriority               = -1              {product}           
+     intx JavaPriority6_To_OSPriority               = -1              {product}           
+     intx JavaPriority7_To_OSPriority               = -1              {product}           
+     intx JavaPriority8_To_OSPriority               = -1              {product}           
+     intx JavaPriority9_To_OSPriority               = -1              {product}           
+     bool LIRFillDelaySlots                         = false           {C1 pd product}     
+    uintx LargePageHeapSizeThreshold                = 134217728       {product}           
+    ccstr LargePageMountPoint                       =                 {product}           
+    uintx LargePageSizeInBytes                      = 0               {product}           
+     bool LazyBootClassLoader                       = true            {product}           
+     bool LinkWellKnownClasses                      = false           {diagnostic}        
+     bool LogAHR                                    = false           {product}           
+     bool LogCMSParallelSTWFullGC                   = false           {product}           
+     bool LogCompilation                            = false           {diagnostic}        
+    ccstr LogFile                                   =                 {diagnostic}        
+     bool LogGCOverheadLimit                        = false           {product}           
+     bool LogVMOutput                               = false           {diagnostic}        
+     bool LoopLimitCheck                            = true            {C2 diagnostic}     
+     intx LoopOptsCount                             = 43              {C2 product}        
+     intx LoopUnrollLimit                           = 50              {C2 pd product}     
+     intx LoopUnrollMin                             = 4               {C2 product}        
+     bool LoopUnswitching                           = true            {C2 product}        
+     intx MallocVerifyInterval                      = 0               {diagnostic}        
+     intx MallocVerifyStart                         = 0               {diagnostic}        
+     bool ManagementServer                          = false           {product}           
+    uintx MarkStackSize                             = 32768           {product}           
+    uintx MarkStackSizeMax                          = 4194304         {product}           
+     intx MarkSweepAlwaysCompactCount               = 4               {product}           
+    uintx MarkSweepDeadRatio                        = 5               {product}           
+     intx MaxBCEAEstimateLevel                      = 5               {product}           
+     intx MaxBCEAEstimateSize                       = 150             {product}           
+     intx MaxDirectMemorySize                       = -1              {product}           
+     bool MaxFDLimit                                = true            {product}           
+    uintx MaxGCMinorPauseMillis                     = 4294967295      {product}           
+    uintx MaxGCPauseMillis                          = 4294967295      {product}           
+    uintx MaxHeapFreeRatio                          = 70              {product}           
+    uintx MaxHeapSize                              := 1073741824      {product}           
+     intx MaxInlineLevel                            = 9               {product}           
+     intx MaxInlineSize                             = 35              {product}           
+     intx MaxJavaStackTraceDepth                    = 1024            {product}           
+     intx MaxJumpTableSize                          = 65000           {C2 product}        
+     intx MaxJumpTableSparseness                    = 5               {C2 product}        
+     intx MaxLabelRootDepth                         = 1100            {C2 product}        
+     intx MaxLoopPad                                = 11              {C2 product}        
+    uintx MaxNewSize                                = 4294901760      {product}           
+     intx MaxNodeLimit                              = 65000           {C2 product}        
+    uintx MaxPermHeapExpansion                      = 4194304         {product}           
+    uintx MaxPermSize                               = 67108864        {pd product}        
+ uint64_t MaxRAM                                    = 0               {pd product}        
+    uintx MaxRAMFraction                            = 4               {product}           
+     intx MaxRecursiveInlineLevel                   = 1               {product}           
+     intx MaxTenuringThreshold                      = 15              {product}           
+     intx MaxTrivialSize                            = 6               {product}           
+     bool MethodFlushing                            = true            {product}           
+     intx MethodHandlePushLimit                     = 3               {diagnostic}        
+     intx MinCodeCacheFlushingInterval              = 30              {product}           
+    uintx MinHeapDeltaBytes                         = 131072          {product}           
+    uintx MinHeapFreeRatio                          = 40              {product}           
+     intx MinInliningThreshold                      = 250             {product}           
+     intx MinJumpTableSize                          = 18              {C2 product}        
+    uintx MinPermHeapExpansion                      = 262144          {product}           
+    uintx MinRAMFraction                            = 2               {product}           
+    uintx MinSurvivorRatio                          = 3               {product}           
+    uintx MinTLABSize                               = 2048            {product}           
+     bool MixedModeThreadDump                       = true            {product}           
+     intx MonitorBound                              = 0               {product}           
+     bool MonitorInUseLists                         = false           {product}           
+     intx MultiArrayExpandLimit                     = 6               {C2 product}        
+     bool MustCallLoadClassInternal                 = false           {product}           
+     intx NUMAChunkResizeWeight                     = 20              {product}           
+    uintx NUMAInterleaveGranularity                 = 2097152         {product}           
+     intx NUMAPageScanRate                          = 256             {product}           
+     intx NUMASpaceResizeRate                       = 1073741824      {product}           
+     bool NUMAStats                                 = false           {product}           
+     intx NativeMonitorFlags                        = 0               {product}           
+     intx NativeMonitorSpinLimit                    = 20              {product}           
+     intx NativeMonitorTimeout                      = -1              {product}           
+     bool NeedsDeoptSuspend                         = false           {pd product}        
+     bool NeverActAsServerClassMachine              = false           {pd product}        
+     bool NeverTenure                               = false           {product}           
+     intx NewRatio                                  = 2               {product}           
+    uintx NewSize                                   = 1048576         {product}           
+    uintx NewSizeThreadIncrease                     = 4096            {pd product}        
+     intx NmethodSweepCheckInterval                 = 5               {product}           
+     intx NmethodSweepFraction                      = 4               {product}           
+     intx NodeLimitFudgeFactor                      = 1000            {C2 product}        
+    uintx NumberOfGCLogFiles                        = 0               {product}           
+     intx NumberOfLoopInstrToAlign                  = 4               {C2 product}        
+     bool ObjLifeTracking                           = false           {product}           
+    uintx ObjLifeTrackingCpuFraction                = 100             {product}           
+    uintx ObjLifetimeHistoBucketCount               = 1024            {product}           
+     bool ObjLifetimeHistoExportVarPerBucket        = false           {product}           
+    uintx OldPLABSize                               = 1024            {product}           
+    uintx OldPLABWeight                             = 50              {product}           
+    uintx OldSize                                   = 4194304         {product}           
+     bool OmitStackTraceInFastThrow                 = true            {product}           
+ccstrlist OnError                                   =                 {product}           
+ccstrlist OnOutOfMemoryError                        =                 {product}           
+     intx OnStackReplacePercentage                  = 140             {pd product}        
+     bool OptimizeFill                              = false           {C2 product}        
+     bool OptimizeMethodHandles                     = true            {diagnostic}        
+     bool OptimizeStringConcat                      = false           {C2 product}        
+     bool OptoBundling                              = false           {C2 pd product}     
+     intx OptoLoopAlignment                         = 16              {pd product}        
+     bool OptoScheduling                            = false           {C2 pd product}     
+    uintx PLABWeight                                = 75              {product}           
+     bool PSChunkLargeArrays                        = true            {product}           
+     bool PSResizeByFreeRatio                       = false           {product}           
+     bool PSResizeByFreeRatioWithSystemGC           = false           {product}           
+     intx ParGCArrayScanChunk                       = 50              {product}           
+     intx ParGCCardsPerStrideChunk                  = 256             {diagnostic}        
+    uintx ParGCDesiredObjsFromOverflowList          = 20              {product}           
+     intx ParGCStridesPerThread                     = 2               {diagnostic}        
+     bool ParGCTrimOverflow                         = true            {product}           
+     bool ParGCUseLocalOverflow                     = false           {product}           
+     intx ParallelGCBufferWastePct                  = 10              {product}           
+     bool ParallelGCRetainPLAB                      = false           {diagnostic}        
+    uintx ParallelGCThreads                        := 10              {product}           
+     bool ParallelGCVerbose                         = false           {product}           
+    uintx ParallelOldDeadWoodLimiterMean            = 50              {product}           
+    uintx ParallelOldDeadWoodLimiterStdDev          = 80              {product}           
+     bool ParallelRefProcBalancingEnabled           = true            {product}           
+     bool ParallelRefProcEnabled                    = false           {product}           
+     bool PartialPeelAtUnsignedTests                = true            {C2 product}        
+     bool PartialPeelLoop                           = true            {C2 product}        
+     intx PartialPeelNewPhiDelta                    = 0               {C2 product}        
+     bool PauseAtExit                               = false           {diagnostic}        
+     bool PauseAtStartup                            = false           {diagnostic}        
+    ccstr PauseAtStartupFile                        =                 {diagnostic}        
+    uintx PausePadding                              = 1               {product}           
+     intx PerBytecodeRecompilationCutoff            = 200             {product}           
+     intx PerBytecodeTrapLimit                      = 4               {product}           
+     intx PerMethodRecompilationCutoff              = 400             {product}           
+     intx PerMethodTrapLimit                        = 100             {product}           
+     bool PerfAllowAtExitRegistration               = false           {product}           
+     bool PerfBypassFileSystemCheck                 = false           {product}           
+     intx PerfDataMemorySize                        = 32768           {product}           
+     intx PerfDataSamplingInterval                  = 50              {product}           
+    ccstr PerfDataSaveFile                          =                 {product}           
+     bool PerfDataSaveToFile                        = false           {product}           
+     bool PerfDisableSharedMem                      = false           {product}           
+     intx PerfMaxStringConstLength                  = 1024            {product}           
+    uintx PermGenPadding                            = 3               {product}           
+    uintx PermMarkSweepDeadRatio                    = 20              {product}           
+    uintx PermSize                                  = 16777216        {pd product}        
+     bool PostSpinYield                             = true            {product}           
+     intx PreBlockSpin                              = 10              {product}           
+     intx PreInflateSpin                            = 10              {pd product}        
+     bool PreSpinYield                              = false           {product}           
+     bool PreferInterpreterNativeStubs              = false           {pd product}        
+     intx PrefetchCopyIntervalInBytes               = -1              {product}           
+     intx PrefetchFieldsAhead                       = -1              {product}           
+     intx PrefetchScanIntervalInBytes               = -1              {product}           
+     bool PreserveAllAnnotations                    = false           {product}           
+    uintx PreserveMarkStackSize                     = 1024            {product}           
+    uintx PretenureSizeThreshold                    = 0               {product}           
+     bool PrintAdapterHandlers                      = false           {diagnostic}        
+     bool PrintAdaptiveSizePolicy                   = false           {product}           
+     bool PrintAssembly                             = false           {diagnostic}        
+    ccstr PrintAssemblyOptions                      =                 {diagnostic}        
+     bool PrintBiasedLockingStatistics              = false           {diagnostic}        
+     bool PrintCMSInitiationCause                   = false           {product}           
+     bool PrintCMSInitiationStatistics              = false           {product}           
+     intx PrintCMSStatistics                        = 0               {product}           
+     bool PrintCardTableStats                       = false           {product}           
+     bool PrintClassHistogram                       = false           {manageable}        
+     bool PrintClassHistogramAfterFullGC            = false           {manageable}        
+     bool PrintClassHistogramBeforeFullGC           = false           {manageable}        
+     bool PrintClassLoadingDateStamps               = false           {product}           
+     bool PrintClassLoadingTimeStamps               = false           {product}           
+     bool PrintCommandLineFlags                     = false           {product}           
+     bool PrintCompilation                          = false           {product}           
+     bool PrintCompressedOopsMode                   = false           {diagnostic}        
+     bool PrintConcurrentLocks                      = false           {manageable}        
+     bool PrintDTraceDOF                            = false           {diagnostic}        
+     intx PrintFLSCensus                            = 0               {product}           
+     intx PrintFLSStatistics                        = 0               {product}           
+     bool PrintFlagsFinal                          := true            {product}           
+     bool PrintFlagsInitial                         = false           {product}           
+     bool PrintFullGCPhaseTimes                     = false           {product}           
+     bool PrintGC                                   = false           {manageable}        
+     bool PrintGCApplicationConcurrentTime          = false           {product}           
+     bool PrintGCApplicationStoppedTime             = false           {product}           
+     bool PrintGCDateStamps                         = false           {manageable}        
+     bool PrintGCDetails                            = false           {manageable}        
+     bool PrintGCTaskTimeStamps                     = false           {product}           
+     bool PrintGCTimeStamps                         = false           {manageable}        
+     bool PrintHeapAtGC                             = false           {product rw}        
+     bool PrintHeapAtGCExtended                     = false           {product rw}        
+     bool PrintHeapAtSIGBREAK                       = true            {product}           
+     bool PrintInlining                             = false           {diagnostic}        
+     bool PrintInterpreter                          = false           {diagnostic}        
+     bool PrintIntrinsics                           = false           {diagnostic}        
+     bool PrintJNIGCStalls                          = false           {product}           
+     bool PrintJNIResolving                         = false           {product}           
+     bool PrintMethodHandleStubs                    = false           {diagnostic}        
+     bool PrintNMethods                             = false           {diagnostic}        
+     bool PrintNativeNMethods                       = false           {diagnostic}        
+     bool PrintOldPLAB                              = false           {product}           
+     bool PrintOopAddress                           = false           {product}           
+     bool PrintPLAB                                 = false           {product}           
+     bool PrintParallelOldGCPhaseTimes              = false           {product}           
+     bool PrintPreciseBiasedLockingStatistics       = false           {C2 diagnostic}     
+     bool PrintPromotionFailure                     = false           {product}           
+     bool PrintReferenceGC                          = false           {product}           
+     bool PrintRevisitStats                         = false           {product}           
+     bool PrintSafepointStatistics                  = false           {product}           
+     intx PrintSafepointStatisticsCount             = 300             {product}           
+     intx PrintSafepointStatisticsTimeout           = -1              {product}           
+     bool PrintSharedSpaces                         = false           {product}           
+     bool PrintSignatureHandlers                    = false           {diagnostic}        
+     bool PrintStubCode                             = false           {diagnostic}        
+     bool PrintTLAB                                 = false           {product}           
+     bool PrintTenuringDistribution                 = false           {product}           
+     bool PrintTieredEvents                         = false           {product}           
+     bool PrintVMOptions                            = false           {product}           
+     bool PrintVMQWaitTime                          = false           {product}           
+     bool PrintWarnings                             = true            {product}           
+    uintx ProcessDistributionStride                 = 4               {product}           
+     bool ProfileDynamicTypes                       = true            {diagnostic}        
+     bool ProfileInterpreter                        = true            {pd product}        
+     bool ProfileIntervals                          = false           {product}           
+     intx ProfileIntervalsTicks                     = 100             {product}           
+     intx ProfileMaturityPercentage                 = 20              {product}           
+     bool ProfileVM                                 = false           {product}           
+     bool ProfilerPrintByteCodeStatistics           = false           {product}           
+     bool ProfilerRecordPC                          = false           {product}           
+    uintx PromotedPadding                           = 3               {product}           
+     intx QueuedAllocationWarningCount              = 0               {product}           
+    uintx RamLimit                                  = 0               {product}           
+     bool RangeCheckElimination                     = true            {product}           
+     bool RangeLimitCheck                           = true            {C2 diagnostic}     
+     intx ReadPrefetchInstr                         = 0               {product}           
+     intx ReadSpinIterations                        = 100             {product}           
+     bool ReassociateInvariants                     = true            {C2 product}        
+     bool ReduceBulkZeroing                         = true            {C2 product}        
+     bool ReduceFieldZeroing                        = true            {C2 product}        
+     bool ReduceInitialCardMarks                    = true            {C2 product}        
+     bool ReduceSignalUsage                         = false           {product}           
+     intx RefDiscoveryPolicy                        = 0               {product}           
+     bool ReflectionWrapResolutionErrors            = true            {product}           
+     bool RegisterFinalizersAtInit                  = true            {product}           
+     bool RelaxAccessControlCheck                   = false           {product}           
+     bool RequireSharedSpaces                       = false           {product}           
+    uintx ReservedCodeCacheSize                     = 50331648        {pd product}        
+     bool ResizeOldPLAB                             = true            {product}           
+     bool ResizePLAB                                = true            {product}           
+     bool ResizeTLAB                                = true            {pd product}        
+     bool RestoreMXCSROnJNICalls                    = false           {product}           
+     bool RewriteBytecodes                          = true            {pd product}        
+     bool RewriteFrequentPairs                      = true            {pd product}        
+     intx SafepointPollOffset                       = 256             {C1 pd product}     
+     intx SafepointSpinBeforeYield                  = 2000            {product}           
+     bool SafepointTimeout                          = false           {product}           
+     intx SafepointTimeoutDelay                     = 10000           {product}           
+     bool ScavengeBeforeFullGC                      = true            {product}           
+     intx ScavengeRootsInCode                       = 1               {diagnostic}        
+     intx SelfDestructTimer                         = 0               {product}           
+     bool SerializeVMOutput                         = true            {diagnostic}        
+     bool ShareCMSMarkBitMapWithParalleSTWFullGC    = true            {product}           
+    uintx SharedDummyBlockSize                      = 536870912       {product}           
+    uintx SharedMiscCodeSize                        = 4194304         {product}           
+    uintx SharedMiscDataSize                        = 4194304         {product}           
+     bool SharedOptimizeColdStart                   = true            {diagnostic}        
+    uintx SharedReadOnlySize                        = 10485760        {product}           
+    uintx SharedReadWriteSize                       = 12582912        {product}           
+     bool SharedSkipVerify                          = false           {diagnostic}        
+     bool ShowMessageBoxOnError                     = false           {product}           
+     intx SoftRefLRUPolicyMSPerMB                   = 1000            {product}           
+     bool SplitIfBlocks                             = true            {product}           
+     intx StackRedPages                             = 1               {pd product}        
+     intx StackShadowPages                          = 3               {pd product}        
+     bool StackTraceInThrowable                     = true            {product}           
+     intx StackYellowPages                          = 2               {pd product}        
+     bool StartAttachListener                       = false           {product}           
+     bool StartSuspended                            = false           {product}           
+     intx StarvationMonitorInterval                 = 200             {product}           
+     bool StressLdcRewrite                          = false           {product}           
+     bool StressTieredRuntime                       = false           {product}           
+    uintx StringTableSize                           = 1009            {product}           
+     bool SuppressFatalErrorMessage                 = false           {product}           
+    uintx SurvivorPadding                           = 3               {product}           
+     intx SurvivorRatio                             = 8               {product}           
+     intx SuspendRetryCount                         = 50              {product}           
+     intx SuspendRetryDelay                         = 5               {product}           
+     intx SyncFlags                                 = 0               {product}           
+    ccstr SyncKnobs                                 =                 {product}           
+     intx SyncVerbose                               = 0               {product}           
+    uintx TLABAllocationWeight                      = 35              {product}           
+    uintx TLABRefillWasteFraction                   = 64              {product}           
+    uintx TLABSize                                  = 0               {product}           
+     bool TLABStats                                 = true            {product}           
+    uintx TLABWasteIncrement                        = 4               {product}           
+    uintx TLABWasteTargetPercent                    = 1               {product}           
+     intx TargetPLABWastePct                        = 10              {product}           
+     intx TargetSurvivorRatio                       = 50              {product}           
+    uintx TenuredGenerationSizeIncrement            = 20              {product}           
+    uintx TenuredGenerationSizeSupplement           = 80              {product}           
+    uintx TenuredGenerationSizeSupplementDecay      = 2               {product}           
+     intx ThreadPriorityPolicy                      = 0               {product}           
+     bool ThreadPriorityVerbose                     = false           {product}           
+    uintx ThreadSafetyMargin                        = 52428800        {product}           
+     intx ThreadStackSize                           = 320             {pd product}        
+    uintx ThresholdTolerance                        = 10              {product}           
+     intx Tier0BackedgeNotifyFreqLog                = 10              {product}           
+     intx Tier0InvokeNotifyFreqLog                  = 7               {product}           
+     intx Tier0ProfilingStartPercentage             = 200             {product}           
+     intx Tier1FreqInlineSize                       = 35              {C2 product}        
+     intx Tier1Inline                               = 0               {C2 product}        
+     intx Tier1LoopOptsCount                        = 0               {C2 product}        
+     intx Tier1MaxInlineSize                        = 8               {C2 product}        
+     intx Tier2BackEdgeThreshold                    = 0               {product}           
+     intx Tier2BackedgeNotifyFreqLog                = 14              {product}           
+     intx Tier2CompileThreshold                     = 0               {product}           
+     intx Tier2InvokeNotifyFreqLog                  = 11              {product}           
+     intx Tier3BackEdgeThreshold                    = 7000            {product}           
+     intx Tier3BackedgeNotifyFreqLog                = 13              {product}           
+     intx Tier3CompileThreshold                     = 2000            {product}           
+     intx Tier3DelayOff                             = 2               {product}           
+     intx Tier3DelayOn                              = 5               {product}           
+     intx Tier3InvocationThreshold                  = 200             {product}           
+     intx Tier3InvokeNotifyFreqLog                  = 10              {product}           
+     intx Tier3LoadFeedback                         = 5               {product}           
+     intx Tier3MinInvocationThreshold               = 100             {product}           
+     intx Tier4BackEdgeThreshold                    = 40000           {product}           
+     intx Tier4CompileThreshold                     = 15000           {product}           
+     intx Tier4InvocationThreshold                  = 5000            {product}           
+     intx Tier4LoadFeedback                         = 3               {product}           
+     intx Tier4MinInvocationThreshold               = 600             {product}           
+     bool TieredCompilation                         = false           {pd product}        
+     intx TieredCompileTaskTimeout                  = 50              {product}           
+     intx TieredRateUpdateMaxTime                   = 25              {product}           
+     intx TieredRateUpdateMinTime                   = 1               {product}           
+     intx TieredStopAtLevel                         = 4               {product}           
+     bool TimeLinearScan                            = false           {C1 product}        
+     bool TraceBiasedLocking                        = false           {product}           
+     bool TraceClassLoading                         = false           {product rw}        
+     bool TraceClassLoadingPreorder                 = false           {product}           
+     bool TraceClassResolution                      = false           {product}           
+     bool TraceClassUnloading                       = false           {product rw}        
+     bool TraceCompileTriggered                     = false           {diagnostic}        
+     bool TraceGen0Time                             = false           {product}           
+     bool TraceGen1Time                             = false           {product}           
+    ccstr TraceJVMTI                                =                 {product}           
+     bool TraceJVMTIObjectTagging                   = false           {diagnostic}        
+     bool TraceLoaderConstraints                    = false           {product rw}        
+     bool TraceMonitorInflation                     = false           {product}           
+     bool TraceNMethodInstalls                      = false           {diagnostic}        
+     bool TraceOSRBreakpoint                        = false           {diagnostic}        
+     bool TraceParallelOldGCTasks                   = false           {product}           
+     intx TraceRedefineClasses                      = 0               {product}           
+     bool TraceRedundantCompiles                    = false           {diagnostic}        
+     bool TraceSafepointCleanupTime                 = false           {product}           
+     bool TraceSuperWord                            = false           {C2 product}        
+     bool TraceSuspendWaitFailures                  = false           {product}           
+     bool TraceTriggers                             = false           {diagnostic}        
+     intx TrackedInitializationLimit                = 50              {C2 product}        
+     bool TransmitErrorReport                       = false           {product}           
+     intx TypeProfileMajorReceiverPercent           = 90              {product}           
+     intx TypeProfileWidth                          = 2               {product}           
+     intx UnguardOnExecutionViolation               = 0               {product}           
+     bool UnlinkSymbolsALot                         = false           {product}           
+     bool UnlockDiagnosticVMOptions                 = true            {diagnostic}        
+     bool UnrollLimitCheck                          = true            {C2 diagnostic}     
+     bool UnsyncloadClass                           = false           {diagnostic}        
+     bool Use486InstrsOnly                          = false           {product}           
+     bool UseAdaptiveGCBoundary                     = false           {product}           
+     bool UseAdaptiveGenerationSizePolicyAtMajorCollection  = true            {product}           
+     bool UseAdaptiveGenerationSizePolicyAtMinorCollection  = true            {product}           
+     bool UseAdaptiveNUMAChunkSizing                = true            {product}           
+     bool UseAdaptiveSizeDecayMajorGCCost           = true            {product}           
+     bool UseAdaptiveSizePolicy                     = true            {product}           
+     bool UseAdaptiveSizePolicyFootprintGoal        = true            {product}           
+     bool UseAdaptiveSizePolicyWithSystemGC         = false           {product}           
+     bool UseAddressNop                             = true            {product}           
+     bool UseAltSigs                                = false           {product}           
+     bool UseAutoGCSelectPolicy                     = false           {product}           
+     bool UseBiasedLocking                          = true            {product}           
+     bool UseBimorphicInlining                      = true            {C2 product}        
+     bool UseBlockCopy                              = false           {product}           
+     bool UseBlockZeroing                           = false           {product}           
+     bool UseBoundThreads                           = true            {product}           
+     bool UseCBCond                                 = false           {product}           
+     bool UseCMSBestFit                             = true            {product}           
+     bool UseCMSCollectionPassing                   = true            {product}           
+     bool UseCMSCompactAtFullCollection             = true            {product}           
+     bool UseCMSInitiatingOccupancyOnly             = false           {product}           
+     bool UseCMSParallelSTWFullGC                   = false           {product}           
+     bool UseCodeCacheFlushing                      = false           {product}           
+     bool UseCompiler                               = true            {product}           
+     bool UseCompilerSafepoints                     = true            {product}           
+     bool UseConcMarkSweepGC                        = false           {product}           
+     bool UseCondCardMark                           = false           {product}           
+     bool UseCountLeadingZerosInstruction           = false           {product}           
+     bool UseCounterDecay                           = true            {product}           
+     bool UseDivMod                                 = true            {C2 product}        
+     bool UseFPUForSpilling                         = false           {C2 product}        
+     bool UseFastAccessorMethods                    = false           {product}           
+     bool UseFastEmptyMethods                       = false           {product}           
+     bool UseFastJNIAccessors                       = true            {product}           
+     bool UseG1GC                                   = false           {product}           
+     bool UseGCLogFileRotation                      = false           {product}           
+     bool UseGCOverheadLimit                        = true            {product}           
+     bool UseGCTaskAffinity                         = false           {product}           
+     bool UseHeavyMonitors                          = false           {product}           
+     bool UseHugeTLBFS                              = false           {product}           
+     bool UseIncDec                                 = true            {diagnostic}        
+     bool UseInlineCaches                           = true            {product}           
+     bool UseInterpreter                            = true            {product}           
+     bool UseJumpTables                             = true            {C2 product}        
+     bool UseLWPSynchronization                     = true            {product}           
+     bool UseLargePages                             = false           {pd product}        
+     bool UseLargePagesIndividualAllocation         = false           {pd product}        
+     bool UseLinuxPosixThreadCPUClocks              = false           {product}           
+     bool UseLoopCounter                            = true            {product}           
+     bool UseLoopPredicate                          = true            {C2 product}        
+     bool UseMaximumCompactionOnSystemGC            = true            {product}           
+     bool UseMembar                                 = false           {pd product}        
+     bool UseNUMA                                   = false           {product}           
+     bool UseNUMAInterleaving                       = false           {product}           
+     bool UseNewCode                                = false           {diagnostic}        
+     bool UseNewCode2                               = false           {diagnostic}        
+     bool UseNewCode3                               = false           {diagnostic}        
+     bool UseNewLongLShift                          = false           {product}           
+     bool UseNiagaraInstrs                          = false           {product}           
+     bool UseOSErrorReporting                       = false           {pd product}        
+     bool UseOldInlining                            = true            {C2 product}        
+     bool UseOnStackReplacement                     = true            {pd product}        
+     bool UseOnlyInlinedBimorphic                   = true            {C2 product}        
+     bool UseOprofile                               = false           {product}           
+     bool UseOptoBiasInlining                       = true            {C2 product}        
+     bool UsePPCLWSYNC                              = true            {product}           
+     bool UsePSAdaptiveSurvivorSizePolicy           = true            {product}           
+     bool UseParNewGC                               = false           {product}           
+     bool UseParallelGC                            := true            {product}           
+     bool UseParallelOldGC                          = false           {product}           
+     bool UsePerfData                               = true            {product}           
+     bool UsePopCountInstruction                    = true            {product}           
+     bool UseRDPCForConstantTableBase               = false           {C2 product}        
+     bool UseRicochetFrames                         = true            {diagnostic}        
+     bool UseSHM                                    = false           {product}           
+     intx UseSSE                                    = 4               {product}           
+     bool UseSSE42Intrinsics                        = true            {product}           
+     bool UseSeparateVSpacesInYoungGen              = true            {product}           
+     bool UseSerialGC                               = false           {product}           
+     bool UseSharedSpaces                           = false           {product}           
+     bool UseSignalChaining                         = true            {product}           
+     bool UseSpinning                               = false           {product}           
+     bool UseSplitVerifier                          = true            {product}           
+     bool UseStoreImmI16                            = false           {product}           
+     bool UseStringCache                            = false           {product}           
+     bool UseSuperWord                              = true            {C2 product}        
+     bool UseTLAB                                   = true            {pd product}        
+     bool UseThreadPriorities                       = true            {pd product}        
+     bool UseTypeProfile                            = true            {product}           
+     bool UseUnalignedLoadStores                    = true            {product}           
+     intx UseVIS                                    = 99              {product}           
+     bool UseVMInterruptibleIO                      = false           {product}           
+     bool UseVectoredExceptions                     = false           {pd product}        
+     bool UseXMMForArrayCopy                        = true            {product}           
+     bool UseXmmI2D                                 = false           {product}           
+     bool UseXmmI2F                                 = false           {product}           
+     bool UseXmmLoadAndClearUpper                   = true            {product}           
+     bool UseXmmRegToRegMoveAll                     = true            {product}           
+     bool VMThreadHintNoPreempt                     = false           {product}           
+     intx VMThreadPriority                          = -1              {product}           
+     intx VMThreadStackSize                         = 512             {pd product}        
+     intx ValueMapInitialSize                       = 11              {C1 product}        
+     intx ValueMapMaxLoopSize                       = 8               {C1 product}        
+     intx ValueSearchLimit                          = 1000            {C2 product}        
+     bool VerifyAfterGC                             = false           {diagnostic}        
+     bool VerifyBeforeExit                          = false           {diagnostic}        
+     bool VerifyBeforeGC                            = false           {diagnostic}        
+     bool VerifyBeforeIteration                     = false           {diagnostic}        
+     bool VerifyDuringGC                            = false           {diagnostic}        
+     intx VerifyGCLevel                             = 0               {diagnostic}        
+    uintx VerifyGCStartAt                           = 0               {diagnostic}        
+     bool VerifyMergedCPBytecodes                   = true            {product}           
+     bool VerifyMethodHandles                       = false           {diagnostic}        
+     bool VerifyObjectStartArray                    = true            {diagnostic}        
+     bool VerifyRememberedSets                      = false           {diagnostic}        
+     intx WorkAroundNPTLTimedWaitHang               = 1               {product}           
+    uintx YoungGenerationSizeIncrement              = 20              {product}           
+    uintx YoungGenerationSizeSupplement             = 80              {product}           
+    uintx YoungGenerationSizeSupplementDecay        = 8               {product}           
+    uintx YoungPLABSize                             = 4096            {product}           
+     bool ZeroTLAB                                  = false           {product}           
+     intx hashCode                                  = 0               {product}           
\ No newline at end of file
diff --git a/caliper/src/test/resources/com/google/caliper/bridge/jdk7-gc.txt b/caliper/src/test/resources/com/google/caliper/bridge/jdk7-gc.txt
new file mode 100644
index 0000000..b3afcf1
--- /dev/null
+++ b/caliper/src/test/resources/com/google/caliper/bridge/jdk7-gc.txt
@@ -0,0 +1,200 @@
+2013-02-11T20:15:26.706-0600: 0.098: [GC 1316K->576K(62848K), 0.0014240 secs]
+2013-02-11T20:15:26.708-0600: 0.099: [Full GC 576K->486K(62848K), 0.0044860 secs]
+2013-02-11T20:15:26.713-0600: 0.104: [GC 486K->486K(62848K), 0.0005000 secs]
+2013-02-11T20:15:26.713-0600: 0.105: [Full GC 486K->486K(62848K), 0.0039840 secs]
+2013-02-11T20:15:26.717-0600: 0.109: [GC 486K->486K(62848K), 0.0007650 secs]
+2013-02-11T20:15:26.718-0600: 0.110: [Full GC 486K->486K(62848K), 0.0038350 secs]
+2013-02-11T20:15:26.722-0600: 0.113: [GC 486K->486K(62848K), 0.0005430 secs]
+2013-02-11T20:15:26.723-0600: 0.114: [Full GC 486K->486K(62848K), 0.0045480 secs]
+2013-02-11T20:15:26.727-0600: 0.119: [GC 486K->486K(62848K), 0.0003950 secs]
+2013-02-11T20:15:26.728-0600: 0.119: [Full GC 486K->486K(62848K), 0.0036570 secs]
+2013-02-11T20:15:26.731-0600: 0.123: [GC 486K->486K(62848K), 0.0005850 secs]
+2013-02-11T20:15:26.732-0600: 0.123: [Full GC 486K->486K(62848K), 0.0036650 secs]
+2013-02-11T20:15:26.736-0600: 0.127: [GC 486K->486K(62848K), 0.0004640 secs]
+2013-02-11T20:15:26.736-0600: 0.128: [Full GC 486K->486K(62848K), 0.0035990 secs]
+2013-02-11T20:15:26.740-0600: 0.131: [GC 486K->486K(62848K), 0.0005500 secs]
+2013-02-11T20:15:26.740-0600: 0.132: [Full GC 486K->486K(62848K), 0.0038150 secs]
+2013-02-11T20:15:26.744-0600: 0.136: [GC 486K->486K(62848K), 0.0004460 secs]
+2013-02-11T20:15:26.745-0600: 0.136: [Full GC 486K->486K(62848K), 0.0036000 secs]
+2013-02-11T20:15:26.748-0600: 0.140: [GC 486K->486K(62848K), 0.0003120 secs]
+2013-02-11T20:15:26.749-0600: 0.140: [Full GC 486K->486K(62848K), 0.0035820 secs]
+2013-02-11T20:15:26.752-0600: 0.144: [GC 486K->486K(62848K), 0.0003340 secs]
+2013-02-11T20:15:26.753-0600: 0.144: [Full GC 486K->486K(62848K), 0.0036170 secs]
+2013-02-11T20:15:26.756-0600: 0.148: [GC 486K->486K(62848K), 0.0004370 secs]
+2013-02-11T20:15:26.757-0600: 0.148: [Full GC 486K->486K(62848K), 0.0036000 secs]
+2013-02-11T20:15:26.761-0600: 0.152: [GC 486K->486K(62848K), 0.0003760 secs]
+2013-02-11T20:15:26.761-0600: 0.152: [Full GC 486K->486K(62848K), 0.0035950 secs]
+2013-02-11T20:15:26.765-0600: 0.156: [GC 486K->486K(62848K), 0.0004000 secs]
+2013-02-11T20:15:26.765-0600: 0.157: [Full GC 486K->486K(62848K), 0.0035760 secs]
+2013-02-11T20:15:26.769-0600: 0.160: [GC 486K->486K(62848K), 0.0003480 secs]
+2013-02-11T20:15:26.769-0600: 0.161: [Full GC 486K->486K(62848K), 0.0035710 secs]
+2013-02-11T20:15:26.773-0600: 0.164: [GC 486K->486K(62848K), 0.0003370 secs]
+2013-02-11T20:15:26.773-0600: 0.164: [Full GC 486K->486K(62848K), 0.0035910 secs]
+2013-02-11T20:15:26.777-0600: 0.168: [GC 486K->486K(62848K), 0.0003840 secs]
+2013-02-11T20:15:26.777-0600: 0.169: [Full GC 486K->486K(62848K), 0.0035760 secs]
+2013-02-11T20:15:26.781-0600: 0.172: [GC 486K->486K(62848K), 0.0003560 secs]
+2013-02-11T20:15:26.781-0600: 0.173: [Full GC 486K->486K(62848K), 0.0035920 secs]
+2013-02-11T20:15:26.785-0600: 0.176: [GC 486K->486K(62848K), 0.0003520 secs]
+2013-02-11T20:15:26.785-0600: 0.177: [Full GC 486K->486K(62848K), 0.0035650 secs]
+2013-02-11T20:15:26.789-0600: 0.180: [GC 486K->486K(62848K), 0.0004280 secs]
+2013-02-11T20:15:26.789-0600: 0.181: [Full GC 486K->486K(62848K), 0.0035460 secs]
+2013-02-11T20:15:26.793-0600: 0.184: [GC 815K->502K(62848K), 0.0003340 secs]
+2013-02-11T20:15:26.793-0600: 0.185: [Full GC 502K->486K(62848K), 0.0037010 secs]
+2013-02-11T20:15:26.797-0600: 0.188: [GC 486K->486K(62848K), 0.0004180 secs]
+2013-02-11T20:15:26.797-0600: 0.189: [Full GC 486K->486K(62848K), 0.0035310 secs]
+2013-02-11T20:15:26.801-0600: 0.193: [GC 486K->486K(62848K), 0.0003840 secs]
+2013-02-11T20:15:26.801-0600: 0.193: [Full GC 486K->486K(62848K), 0.0035550 secs]
+2013-02-11T20:15:26.805-0600: 0.197: [GC 486K->486K(62848K), 0.0003240 secs]
+2013-02-11T20:15:26.805-0600: 0.197: [Full GC 486K->486K(62848K), 0.0035560 secs]
+2013-02-11T20:15:26.809-0600: 0.201: [GC 486K->486K(62848K), 0.0002990 secs]
+2013-02-11T20:15:26.809-0600: 0.201: [Full GC 486K->486K(62848K), 0.0035600 secs]
+2013-02-11T20:15:26.813-0600: 0.204: [GC 486K->486K(62848K), 0.0002760 secs]
+2013-02-11T20:15:26.813-0600: 0.205: [Full GC 486K->486K(62848K), 0.0035470 secs]
+2013-02-11T20:15:26.817-0600: 0.208: [GC 486K->486K(62848K), 0.0002980 secs]
+2013-02-11T20:15:26.817-0600: 0.209: [Full GC 486K->486K(62848K), 0.0035440 secs]
+2013-02-11T20:15:26.821-0600: 0.212: [GC 486K->486K(62848K), 0.0002770 secs]
+2013-02-11T20:15:26.821-0600: 0.213: [Full GC 486K->486K(62848K), 0.0035510 secs]
+2013-02-11T20:15:26.825-0600: 0.216: [GC 486K->486K(62848K), 0.0002340 secs]
+2013-02-11T20:15:26.825-0600: 0.216: [Full GC 486K->486K(62848K), 0.0035280 secs]
+2013-02-11T20:15:26.828-0600: 0.220: [GC 486K->486K(62848K), 0.0002440 secs]
+2013-02-11T20:15:26.829-0600: 0.220: [Full GC 486K->486K(62848K), 0.0035330 secs]
+2013-02-11T20:15:26.832-0600: 0.224: [GC 486K->486K(62848K), 0.0003140 secs]
+2013-02-11T20:15:26.833-0600: 0.224: [Full GC 486K->486K(62848K), 0.0035600 secs]
+2013-02-11T20:15:26.836-0600: 0.228: [GC 486K->486K(62848K), 0.0002780 secs]
+2013-02-11T20:15:26.837-0600: 0.228: [Full GC 486K->486K(62848K), 0.0035240 secs]
+2013-02-11T20:15:26.840-0600: 0.232: [GC 486K->486K(62848K), 0.0003130 secs]
+2013-02-11T20:15:26.840-0600: 0.232: [Full GC 486K->486K(62848K), 0.0035350 secs]
+2013-02-11T20:15:26.844-0600: 0.236: [GC 815K->502K(62848K), 0.0002610 secs]
+2013-02-11T20:15:26.844-0600: 0.236: [Full GC 502K->487K(62848K), 0.0035270 secs]
+2013-02-11T20:15:26.848-0600: 0.239: [GC 487K->487K(62848K), 0.0002930 secs]
+2013-02-11T20:15:26.848-0600: 0.240: [Full GC 487K->487K(62848K), 0.0035470 secs]
+2013-02-11T20:15:26.852-0600: 0.243: [GC 487K->487K(62848K), 0.0002730 secs]
+2013-02-11T20:15:26.852-0600: 0.244: [Full GC 487K->487K(62848K), 0.0035470 secs]
+2013-02-11T20:15:26.856-0600: 0.247: [GC 487K->487K(62848K), 0.0003420 secs]
+2013-02-11T20:15:26.856-0600: 0.248: [Full GC 487K->487K(62848K), 0.0035390 secs]
+2013-02-11T20:15:26.860-0600: 0.251: [GC 487K->487K(62848K), 0.0002320 secs]
+2013-02-11T20:15:26.860-0600: 0.252: [Full GC 487K->487K(62848K), 0.0035050 secs]
+2013-02-11T20:15:26.864-0600: 0.255: [GC 487K->487K(62848K), 0.0002880 secs]
+2013-02-11T20:15:26.864-0600: 0.255: [Full GC 487K->487K(62848K), 0.0035190 secs]
+2013-02-11T20:15:26.867-0600: 0.259: [GC 487K->487K(62848K), 0.0002650 secs]
+2013-02-11T20:15:26.868-0600: 0.259: [Full GC 487K->487K(62848K), 0.0034950 secs]
+2013-02-11T20:15:26.871-0600: 0.263: [GC 487K->487K(62848K), 0.0003850 secs]
+2013-02-11T20:15:26.872-0600: 0.263: [Full GC 487K->487K(62848K), 0.0035450 secs]
+2013-02-11T20:15:26.875-0600: 0.267: [GC 487K->487K(62848K), 0.0003630 secs]
+2013-02-11T20:15:26.876-0600: 0.267: [Full GC 487K->487K(62848K), 0.0035380 secs]
+2013-02-11T20:15:26.879-0600: 0.271: [GC 487K->487K(62848K), 0.0002800 secs]
+2013-02-11T20:15:26.879-0600: 0.271: [Full GC 487K->487K(62848K), 0.0035340 secs]
+2013-02-11T20:15:26.883-0600: 0.275: [GC 487K->487K(62848K), 0.0002580 secs]
+2013-02-11T20:15:26.883-0600: 0.275: [Full GC 487K->487K(62848K), 0.0035550 secs]
+2013-02-11T20:15:26.887-0600: 0.278: [GC 487K->487K(62848K), 0.0003880 secs]
+2013-02-11T20:15:26.887-0600: 0.279: [Full GC 487K->487K(62848K), 0.0035690 secs]
+2013-02-11T20:15:26.891-0600: 0.283: [GC 487K->487K(62848K), 0.0003040 secs]
+2013-02-11T20:15:26.891-0600: 0.283: [Full GC 487K->487K(62848K), 0.0035720 secs]
+2013-02-11T20:15:26.895-0600: 0.286: [GC 487K->487K(62848K), 0.0002750 secs]
+2013-02-11T20:15:26.895-0600: 0.287: [Full GC 487K->487K(62848K), 0.0035700 secs]
+2013-02-11T20:15:26.899-0600: 0.290: [GC 816K->503K(62848K), 0.0002830 secs]
+2013-02-11T20:15:26.899-0600: 0.291: [Full GC 503K->487K(62848K), 0.0037630 secs]
+2013-02-11T20:15:26.903-0600: 0.295: [GC 487K->487K(62848K), 0.0002890 secs]
+2013-02-11T20:15:26.903-0600: 0.295: [Full GC 487K->487K(62848K), 0.0037330 secs]
+2013-02-11T20:15:26.907-0600: 0.299: [GC 487K->487K(62848K), 0.0002580 secs]
+2013-02-11T20:15:26.907-0600: 0.299: [Full GC 487K->487K(62848K), 0.0035480 secs]
+2013-02-11T20:15:26.911-0600: 0.303: [GC 816K->503K(62848K), 0.0003540 secs]
+2013-02-11T20:15:26.911-0600: 0.303: [Full GC 503K->487K(62848K), 0.0035610 secs]
+2013-02-11T20:15:26.915-0600: 0.307: [GC 487K->487K(62848K), 0.0004160 secs]
+2013-02-11T20:15:26.915-0600: 0.307: [Full GC 487K->487K(62848K), 0.0035390 secs]
+2013-02-11T20:15:26.919-0600: 0.311: [GC 816K->519K(62848K), 0.0003650 secs]
+2013-02-11T20:15:26.920-0600: 0.311: [Full GC 519K->487K(62848K), 0.0035430 secs]
+2013-02-11T20:15:26.923-0600: 0.315: [GC 487K->487K(62848K), 0.0003340 secs]
+2013-02-11T20:15:26.924-0600: 0.315: [Full GC 487K->487K(62848K), 0.0035600 secs]
+2013-02-11T20:15:26.927-0600: 0.319: [GC 816K->503K(62848K), 0.0003750 secs]
+2013-02-11T20:15:26.928-0600: 0.319: [Full GC 503K->487K(62848K), 0.0035330 secs]
+2013-02-11T20:15:26.931-0600: 0.323: [GC 487K->487K(62848K), 0.0003790 secs]
+2013-02-11T20:15:26.932-0600: 0.323: [Full GC 487K->487K(62848K), 0.0035490 secs]
+2013-02-11T20:15:26.935-0600: 0.327: [GC 816K->503K(62848K), 0.0002930 secs]
+2013-02-11T20:15:26.936-0600: 0.327: [Full GC 503K->487K(62848K), 0.0035400 secs]
+2013-02-11T20:15:26.939-0600: 0.331: [GC 487K->487K(62848K), 0.0002870 secs]
+2013-02-11T20:15:26.939-0600: 0.331: [Full GC 487K->487K(62848K), 0.0035630 secs]
+2013-02-11T20:15:26.943-0600: 0.335: [GC 487K->487K(62848K), 0.0003790 secs]
+2013-02-11T20:15:26.943-0600: 0.335: [Full GC 487K->487K(62848K), 0.0035950 secs]
+2013-02-11T20:15:26.947-0600: 0.339: [GC 816K->503K(62848K), 0.0003640 secs]
+2013-02-11T20:15:26.948-0600: 0.339: [Full GC 503K->487K(62848K), 0.0038120 secs]
+2013-02-11T20:15:26.951-0600: 0.343: [GC 487K->487K(62848K), 0.0003520 secs]
+2013-02-11T20:15:26.952-0600: 0.343: [Full GC 487K->487K(62848K), 0.0035580 secs]
+2013-02-11T20:15:26.955-0600: 0.347: [GC 487K->487K(62848K), 0.0003620 secs]
+2013-02-11T20:15:26.956-0600: 0.347: [Full GC 487K->487K(62848K), 0.0035290 secs]
+2013-02-11T20:15:26.959-0600: 0.351: [GC 487K->487K(62848K), 0.0003630 secs]
+2013-02-11T20:15:26.960-0600: 0.351: [Full GC 487K->487K(62848K), 0.0035080 secs]
+2013-02-11T20:15:26.963-0600: 0.355: [GC 487K->487K(62848K), 0.0003310 secs]
+2013-02-11T20:15:26.964-0600: 0.355: [Full GC 487K->482K(62848K), 0.0038060 secs]
+2013-02-11T20:15:26.968-0600: 0.359: [GC 482K->482K(62848K), 0.0003650 secs]
+2013-02-11T20:15:26.968-0600: 0.359: [Full GC 482K->482K(62848K), 0.0035340 secs]
+2013-02-11T20:15:26.972-0600: 0.363: [GC 482K->482K(62848K), 0.0003300 secs]
+2013-02-11T20:15:26.972-0600: 0.363: [Full GC 482K->482K(62848K), 0.0035340 secs]
+2013-02-11T20:15:26.976-0600: 0.367: [GC 482K->482K(62848K), 0.0003220 secs]
+2013-02-11T20:15:26.976-0600: 0.367: [Full GC 482K->482K(62848K), 0.0035230 secs]
+2013-02-11T20:15:26.979-0600: 0.371: [GC 482K->482K(62848K), 0.0003220 secs]
+2013-02-11T20:15:26.980-0600: 0.371: [Full GC 482K->482K(62848K), 0.0035200 secs]
+2013-02-11T20:15:26.983-0600: 0.375: [GC 482K->482K(62848K), 0.0003030 secs]
+2013-02-11T20:15:26.984-0600: 0.375: [Full GC 482K->482K(62848K), 0.0035370 secs]
+2013-02-11T20:15:26.987-0600: 0.379: [GC 482K->482K(62848K), 0.0003230 secs]
+2013-02-11T20:15:26.988-0600: 0.379: [Full GC 482K->482K(62848K), 0.0035150 secs]
+2013-02-11T20:15:26.991-0600: 0.383: [GC 482K->482K(62848K), 0.0003050 secs]
+2013-02-11T20:15:26.992-0600: 0.383: [Full GC 482K->482K(62848K), 0.0035080 secs]
+2013-02-11T20:15:26.995-0600: 0.387: [GC 482K->482K(62848K), 0.0003320 secs]
+2013-02-11T20:15:26.995-0600: 0.387: [Full GC 482K->482K(62848K), 0.0035060 secs]
+2013-02-11T20:15:26.999-0600: 0.391: [GC 482K->482K(62848K), 0.0002520 secs]
+2013-02-11T20:15:26.999-0600: 0.391: [Full GC 482K->482K(62848K), 0.0035190 secs]
+2013-02-11T20:15:27.003-0600: 0.394: [GC 482K->482K(62848K), 0.0003050 secs]
+2013-02-11T20:15:27.003-0600: 0.395: [Full GC 482K->482K(62848K), 0.0035430 secs]
+2013-02-11T20:15:27.007-0600: 0.398: [GC 482K->482K(62848K), 0.0002800 secs]
+2013-02-11T20:15:27.007-0600: 0.399: [Full GC 482K->482K(62848K), 0.0035210 secs]
+2013-02-11T20:15:27.011-0600: 0.402: [GC 482K->482K(62848K), 0.0003130 secs]
+2013-02-11T20:15:27.011-0600: 0.403: [Full GC 482K->482K(62848K), 0.0035530 secs]
+2013-02-11T20:15:27.015-0600: 0.406: [GC 482K->482K(62848K), 0.0004020 secs]
+2013-02-11T20:15:27.015-0600: 0.407: [Full GC 482K->482K(62848K), 0.0035340 secs]
+2013-02-11T20:15:27.019-0600: 0.410: [GC 482K->482K(62848K), 0.0003310 secs]
+2013-02-11T20:15:27.019-0600: 0.411: [Full GC 482K->482K(62848K), 0.0035390 secs]
+2013-02-11T20:15:27.023-0600: 0.414: [GC 482K->482K(62848K), 0.0003630 secs]
+2013-02-11T20:15:27.023-0600: 0.414: [Full GC 482K->482K(62848K), 0.0035500 secs]
+2013-02-11T20:15:27.027-0600: 0.418: [GC 482K->482K(62848K), 0.0003430 secs]
+2013-02-11T20:15:27.027-0600: 0.418: [Full GC 482K->482K(62848K), 0.0035220 secs]
+2013-02-11T20:15:27.031-0600: 0.422: [GC 482K->482K(62848K), 0.0003260 secs]
+2013-02-11T20:15:27.031-0600: 0.422: [Full GC 482K->482K(62848K), 0.0035230 secs]
+2013-02-11T20:15:27.034-0600: 0.426: [GC 482K->482K(62848K), 0.0003650 secs]
+2013-02-11T20:15:27.035-0600: 0.426: [Full GC 482K->482K(62848K), 0.0035010 secs]
+2013-02-11T20:15:27.038-0600: 0.430: [GC 482K->482K(62848K), 0.0003000 secs]
+2013-02-11T20:15:27.039-0600: 0.430: [Full GC 482K->482K(62848K), 0.0035090 secs]
+2013-02-11T20:15:27.042-0600: 0.434: [GC 482K->482K(62848K), 0.0004570 secs]
+2013-02-11T20:15:27.043-0600: 0.434: [Full GC 482K->482K(62848K), 0.0036010 secs]
+2013-02-11T20:15:27.046-0600: 0.438: [GC 482K->482K(62848K), 0.0004570 secs]
+2013-02-11T20:15:27.047-0600: 0.438: [Full GC 482K->482K(62848K), 0.0036090 secs]
+2013-02-11T20:15:27.051-0600: 0.442: [GC 482K->482K(62848K), 0.0003740 secs]
+2013-02-11T20:15:27.051-0600: 0.442: [Full GC 482K->482K(62848K), 0.0036140 secs]
+2013-02-11T20:15:27.055-0600: 0.446: [GC 482K->482K(62848K), 0.0003630 secs]
+2013-02-11T20:15:27.055-0600: 0.446: [Full GC 482K->482K(62848K), 0.0036080 secs]
+2013-02-11T20:15:27.059-0600: 0.450: [GC 482K->482K(62848K), 0.0003770 secs]
+2013-02-11T20:15:27.059-0600: 0.451: [Full GC 482K->482K(62848K), 0.0036060 secs]
+2013-02-11T20:15:27.063-0600: 0.454: [GC 482K->482K(62848K), 0.0003410 secs]
+2013-02-11T20:15:27.063-0600: 0.455: [Full GC 482K->482K(62848K), 0.0036300 secs]
+2013-02-11T20:15:27.067-0600: 0.458: [GC 482K->482K(62848K), 0.0002990 secs]
+2013-02-11T20:15:27.067-0600: 0.459: [Full GC 482K->482K(62848K), 0.0036160 secs]
+2013-02-11T20:15:27.071-0600: 0.462: [GC 482K->482K(62848K), 0.0003970 secs]
+2013-02-11T20:15:27.071-0600: 0.463: [Full GC 482K->482K(62848K), 0.0036660 secs]
+2013-02-11T20:15:27.075-0600: 0.466: [GC 482K->482K(62848K), 0.0003160 secs]
+2013-02-11T20:15:27.075-0600: 0.467: [Full GC 482K->482K(62848K), 0.0036080 secs]
+2013-02-11T20:15:27.079-0600: 0.470: [GC 482K->482K(62848K), 0.0003140 secs]
+2013-02-11T20:15:27.079-0600: 0.471: [Full GC 482K->482K(62848K), 0.0035740 secs]
+2013-02-11T20:15:27.083-0600: 0.474: [GC 482K->482K(62848K), 0.0003450 secs]
+2013-02-11T20:15:27.083-0600: 0.475: [Full GC 482K->482K(62848K), 0.0036240 secs]
+2013-02-11T20:15:27.087-0600: 0.478: [GC 482K->482K(62848K), 0.0004670 secs]
+2013-02-11T20:15:27.087-0600: 0.479: [Full GC 482K->482K(62848K), 0.0037790 secs]
+2013-02-11T20:15:27.091-0600: 0.483: [GC 482K->482K(62848K), 0.0002690 secs]
+2013-02-11T20:15:27.091-0600: 0.483: [Full GC 482K->482K(62848K), 0.0036290 secs]
+2013-02-11T20:15:27.095-0600: 0.487: [GC 482K->482K(62848K), 0.0003340 secs]
+2013-02-11T20:15:27.096-0600: 0.487: [Full GC 482K->482K(62848K), 0.0035950 secs]
+2013-02-11T20:15:27.099-0600: 0.491: [GC 482K->482K(62848K), 0.0002480 secs]
+2013-02-11T20:15:27.099-0600: 0.491: [Full GC 482K->482K(62848K), 0.0035940 secs]
+2013-02-11T20:15:27.103-0600: 0.495: [GC 482K->482K(62848K), 0.0003510 secs]
+2013-02-11T20:15:27.104-0600: 0.495: [Full GC 482K->482K(62848K), 0.0037060 secs]
+2013-02-11T20:15:27.107-0600: 0.499: [GC 482K->482K(62848K), 0.0003680 secs]
+2013-02-11T20:15:27.108-0600: 0.499: [Full GC 482K->482K(62848K), 0.0035920 secs]
\ No newline at end of file
diff --git a/examples/pom.xml b/examples/pom.xml
index 7352d7b..0e904cb 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -1,19 +1,57 @@
-<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">
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 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.
+  -->
+
+<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>
+    <version>7</version>
   </parent>
-  <url>http://code.google.com/p/caliper/</url>
+
+  <groupId>com.google.caliper</groupId>
+  <artifactId>caliper-examples</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>Caliper Examples</name>
   <description>Caliper Examples</description>
+
+  <url>http://code.google.com/p/caliper/</url>
+
+  <inceptionYear>2009</inceptionYear>
+
+  <organization>
+    <name>Google Inc.</name>
+    <url>http://www.google.com</url>
+  </organization>
+
+  <developers>
+    <developer>
+      <name>Gregory Kick</name>
+      <organization>Google Inc.</organization>
+    </developer>
+    <developer>
+      <name>Jesse Wilson</name>
+    </developer>
+  </developers>
+
   <licenses>
     <license>
       <name>The Apache Software License, Version 2.0</name>
@@ -21,24 +59,23 @@
       <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>
+      <version>1.0-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>junit</groupId>
@@ -47,13 +84,14 @@
       <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>
+        <version>3.2</version>
         <configuration>
           <source>1.6</source>
           <target>1.6</target>
@@ -62,7 +100,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-eclipse-plugin</artifactId>
-        <version>2.8</version>
+        <version>2.9</version>
         <configuration>
           <downloadSources>true</downloadSources>
           <downloadJavadocs>true</downloadJavadocs>
@@ -72,17 +110,12 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-release-plugin</artifactId>
-        <version>2.1</version>
+        <version>2.5.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
index f42390f..81be2a6 100644
--- a/examples/src/main/java/examples/ArraySortBenchmark.java
+++ b/examples/src/main/java/examples/ArraySortBenchmark.java
@@ -16,16 +16,17 @@
 
 package examples;
 
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
 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 {
+public class ArraySortBenchmark {
 
   @Param({"10", "100", "1000", "10000"}) private int length;
 
@@ -34,12 +35,12 @@
   private int[] values;
   private int[] copy;
 
-  @Override protected void setUp() throws Exception {
+  @BeforeExperiment void setUp() throws Exception {
     values = distribution.create(length);
     copy = new int[length];
   }
 
-  public void timeSort(int reps) {
+  @Benchmark void sort(int reps) {
     for (int i = 0; i < reps; i++) {
       System.arraycopy(values, 0, copy, 0, values.length);
       Arrays.sort(copy);
@@ -95,8 +96,4 @@
 
     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
index 3154b01..98cdcc8 100644
--- a/examples/src/main/java/examples/BitSetBenchmark.java
+++ b/examples/src/main/java/examples/BitSetBenchmark.java
@@ -16,8 +16,10 @@
 
 package examples;
 
-import com.google.caliper.Runner;
-import com.google.caliper.SimpleBenchmark;
+
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
+
 import java.util.BitSet;
 import java.util.Random;
 
@@ -44,17 +46,17 @@
  * BaselineIteration   68 XX|||||||||||||||||
  * </pre>
  *
- * <p>Initially things look simple. The {@link #timeSetBitSetX64(int)} benchmark
- * takes approximately twice as long as {@link #timeSetMaskX64(int)}. However
+ * <p>Initially things look simple. The {@link #setBitSetX64(int)} benchmark
+ * takes approximately twice as long as {@link #setMaskX64(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)}
+ * <p>The {@link #charsToMask(int)} and {@link #charsToBitSet(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)}
+ * <p>Additionally the {@link BitSetBenchmark#baselineIteration(int)}
  * benchmark attempts to measure the raw cost of looping through and reading the
  * source data.
  *
@@ -83,11 +85,11 @@
  * <p><b>2:</b>Overly simplistic benchmarks can give a very false impression of
  * performance.
  */
-public class BitSetBenchmark extends SimpleBenchmark {
+public class BitSetBenchmark {
   private BitSet bitSet;
   private char[] bitString;
 
-  @Override protected void setUp() throws Exception {
+  @BeforeExperiment void setUp() throws Exception {
     bitSet = new BitSet(64);
     bitString = new char[64];
     Random r = new Random();
@@ -99,7 +101,7 @@
   /**
    * This benchmark attempts to measure performance of {@link BitSet#set}.
    */
-  public int timeSetBitSetX64(int reps) {
+  @Benchmark int setBitSetX64(int reps) {
     long count = 64L * reps;
     for (int i = 0; i < count; i++) {
       bitSet.set(i & 0x3F, true);
@@ -110,7 +112,7 @@
   /**
    * This benchmark attempts to measure performance of direct bit-manipulation.
    */
-  public long timeSetMaskX64(int reps) {
+  @Benchmark long setMaskX64(int reps) {
     long count = 64L * reps;
     long bitMask = 0L;
     for (int i = 0; i < count; i++) {
@@ -122,9 +124,9 @@
   /**
    * 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)}.
+   * {@link #charsToMask(int)}.
    */
-  public String timeCharsToBitSet(int reps) {
+  @Benchmark String charsToBitSet(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
@@ -141,9 +143,9 @@
   /**
    * 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)}.
+   * {@link #charsToBitSet(int)}.
    */
-  public long timeCharsToMask(int reps) {
+  @Benchmark long charsToMask(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.
@@ -164,12 +166,12 @@
 
   /**
    * This benchmark attempts to measure the baseline cost of both
-   * {@link #timeCharsToBitSet(int)} and {@link #timeCharsToMask(int)}.
+   * {@link #charsToBitSet(int)} and {@link #charsToMask(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) {
+  @Benchmark long baselineIteration(int reps) {
     int badHash = 0;
     for (int i = 0; i < reps; i++) {
       for (int n = 0; n < bitString.length; n++) {
@@ -178,9 +180,4 @@
     }
     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
index 1e013af..82c4439 100644
--- a/examples/src/main/java/examples/CharacterBenchmark.java
+++ b/examples/src/main/java/examples/CharacterBenchmark.java
@@ -16,15 +16,15 @@
 
 package examples;
 
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
 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 {
+public class CharacterBenchmark {
 
     @Param private CharacterSet characterSet;
 
@@ -32,7 +32,7 @@
 
     private char[] chars;
 
-    @Override protected void setUp() throws Exception {
+    @BeforeExperiment void setUp() throws Exception {
         this.chars = characterSet.chars;
     }
 
@@ -51,7 +51,7 @@
     }
 
     // A fake benchmark to give us a baseline.
-    public boolean timeIsSpace(int reps) {
+    @Benchmark boolean isSpace(int reps) {
         boolean dummy = false;
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
@@ -69,7 +69,7 @@
         return dummy;
     }
 
-    public void timeDigit(int reps) {
+    @Benchmark void digit(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -85,7 +85,7 @@
         }
     }
 
-    public void timeGetNumericValue(int reps) {
+    @Benchmark void getNumericValue(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -101,7 +101,7 @@
         }
     }
 
-    public void timeIsDigit(int reps) {
+    @Benchmark void isDigit(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -117,7 +117,7 @@
         }
     }
 
-    public void timeIsIdentifierIgnorable(int reps) {
+    @Benchmark void isIdentifierIgnorable(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -133,7 +133,7 @@
         }
     }
 
-    public void timeIsJavaIdentifierPart(int reps) {
+    @Benchmark void isJavaIdentifierPart(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -149,7 +149,7 @@
         }
     }
 
-    public void timeIsJavaIdentifierStart(int reps) {
+    @Benchmark void isJavaIdentifierStart(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -165,7 +165,7 @@
         }
     }
 
-    public void timeIsLetter(int reps) {
+    @Benchmark void isLetter(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -181,7 +181,7 @@
         }
     }
 
-    public void timeIsLetterOrDigit(int reps) {
+    @Benchmark void isLetterOrDigit(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -197,7 +197,7 @@
         }
     }
 
-    public void timeIsLowerCase(int reps) {
+    @Benchmark void isLowerCase(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -213,7 +213,7 @@
         }
     }
 
-    public void timeIsSpaceChar(int reps) {
+    @Benchmark void isSpaceChar(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -229,7 +229,7 @@
         }
     }
 
-    public void timeIsUpperCase(int reps) {
+    @Benchmark void isUpperCase(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -245,7 +245,7 @@
         }
     }
 
-    public void timeIsWhitespace(int reps) {
+    @Benchmark void isWhitespace(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -261,7 +261,7 @@
         }
     }
 
-    public void timeToLowerCase(int reps) {
+    @Benchmark void toLowerCase(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -277,7 +277,7 @@
         }
     }
 
-    public void timeToUpperCase(int reps) {
+    @Benchmark void toUpperCase(int reps) {
         if (overload == Overload.CHAR) {
             for (int i = 0; i < reps; ++i) {
                 for (int ch = 0; ch < 65536; ++ch) {
@@ -292,9 +292,4 @@
             }
         }
     }
-
-    // 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
index 90ddd39..d4fed5e 100644
--- a/examples/src/main/java/examples/CompressionSizeBenchmark.java
+++ b/examples/src/main/java/examples/CompressionSizeBenchmark.java
@@ -16,10 +16,9 @@
 
 package examples;
 
+import com.google.caliper.Benchmark;
 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;
@@ -30,7 +29,7 @@
 /**
  * Example "arbitrary measurement" benchmark.
  */
-public class CompressionSizeBenchmark extends Benchmark {
+public class CompressionSizeBenchmark {
 
   @Param({
       "this string will compress badly",
@@ -50,7 +49,7 @@
       compressionLevelMap.put("huffmanOnly", Deflater.HUFFMAN_ONLY);
   }
 
-  public long timeSimpleCompression(int reps) {
+  @Benchmark long simpleCompression(int reps) {
     long dummy = 0;
     for (int i = 0; i < reps; i++) {
       dummy += compress(toCompress.getBytes()).length;
@@ -83,8 +82,4 @@
     }
     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
index 01bb8c6..26d2eb9 100644
--- a/examples/src/main/java/examples/ContainsBenchmark.java
+++ b/examples/src/main/java/examples/ContainsBenchmark.java
@@ -16,20 +16,19 @@
 
 package examples;
 
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
 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 {
+public class ContainsBenchmark {
   @Param({"0", "25", "50", "75", "100"}) private int percentNulls;
   @Param({"100", "1000", "10000"}) private int containsPerRep;
 
@@ -39,7 +38,7 @@
   /** twenty-five percent nulls */
   private final List<Object> queries = new ArrayList<Object>();
 
-  @Override protected void setUp() {
+  @BeforeExperiment void setUp() {
     set.addAll(Arrays.asList("str1", "str2", "str3", "str4"));
     int nullThreshold = percentNulls * containsPerRep / 100;
     for (int i = 0; i < nullThreshold; i++) {
@@ -51,28 +50,11 @@
     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) {
+  @Benchmark void contains(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
index fed0950..543db5b 100644
--- a/examples/src/main/java/examples/CopyArrayBenchmark.java
+++ b/examples/src/main/java/examples/CopyArrayBenchmark.java
@@ -16,9 +16,9 @@
 
 package examples;
 
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
 import com.google.caliper.Param;
-import com.google.caliper.Runner;
-import com.google.caliper.SimpleBenchmark;
 
 import java.util.Arrays;
 import java.util.Random;
@@ -44,7 +44,7 @@
  *     memory (boolean arrays count as byte arrays!).
  * </ul>
  */
-public class CopyArrayBenchmark extends SimpleBenchmark {
+public class CopyArrayBenchmark {
   public enum Strategy {
     CLONE {
       @Override Object[] copy(Object[] array) {
@@ -252,7 +252,7 @@
   long[] longArray;
   short[] shortArray;
 
-  @Override protected void setUp() {
+  @BeforeExperiment void setUp() {
     objectArray = new Object[size];
     booleanArray = new boolean[size];
     byteArray = new byte[size];
@@ -271,86 +271,82 @@
       byteArray[i] = (byte) num;
       charArray[i] = (char) num;
       doubleArray[i] = num;
-      floatArray[i] = (float) num;
+      floatArray[i] = num;
       intArray[i] = num;
       longArray[i] = num;
       shortArray[i] = (short) num;
     }
   }
 
-  public int timeObjects(int reps) {
+  @Benchmark int objects(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
-      dummy += strategy.copy(objectArray).hashCode();
+      dummy += System.identityHashCode(strategy.copy(objectArray));
     }
     return dummy;
   }
 
-  public int timeBooleans(int reps) {
+  @Benchmark int booleans(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
-      dummy += strategy.copy(booleanArray).hashCode();
+      dummy += System.identityHashCode(strategy.copy(booleanArray));
     }
     return dummy;
   }
 
-  public int timeBytes(int reps) {
+  @Benchmark int bytes(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
-      dummy += strategy.copy(byteArray).hashCode();
+      dummy += System.identityHashCode(strategy.copy(byteArray));
     }
     return dummy;
   }
 
-  public int timeChars(int reps) {
+  @Benchmark int chars(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
-      dummy += strategy.copy(charArray).hashCode();
+      dummy += System.identityHashCode(strategy.copy(charArray));
     }
     return dummy;
   }
 
-  public int timeDoubles(int reps) {
+  @Benchmark int doubles(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
-      dummy += strategy.copy(doubleArray).hashCode();
+      dummy += System.identityHashCode(strategy.copy(doubleArray));
     }
     return dummy;
   }
 
-  public int timeFloats(int reps) {
+  @Benchmark int floats(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
-      dummy += strategy.copy(floatArray).hashCode();
+      dummy += System.identityHashCode(strategy.copy(floatArray));
     }
     return dummy;
   }
 
-  public int timeInts(int reps) {
+  @Benchmark int ints(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
-      dummy += strategy.copy(intArray).hashCode();
+      dummy += System.identityHashCode(strategy.copy(intArray));
     }
     return dummy;
   }
 
-  public int timeLongs(int reps) {
+  @Benchmark int longs(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
-      dummy += strategy.copy(longArray).hashCode();
+      dummy += System.identityHashCode(strategy.copy(longArray));
     }
     return dummy;
   }
 
-  public int timeShorts(int reps) {
+  @Benchmark int shorts(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
-      dummy += strategy.copy(shortArray).hashCode();
+      dummy += System.identityHashCode(strategy.copy(shortArray));
     }
     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
index 3b7e2dd..3313eee 100644
--- a/examples/src/main/java/examples/DemoBenchmark.java
+++ b/examples/src/main/java/examples/DemoBenchmark.java
@@ -16,23 +16,24 @@
 
 package examples;
 
+import com.google.caliper.AfterExperiment;
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
 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.api.VmOptions;
 import com.google.caliper.util.ShortDuration;
 
 import java.math.BigDecimal;
 
-public class DemoBenchmark extends Benchmark {
+@VmOptions("-server")
+public class DemoBenchmark {
   @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;
@@ -42,13 +43,13 @@
 //    System.out.println("I should not do this.");
   }
 
-  @Override protected void setUp() throws Exception {
+  @BeforeExperiment void setUp() throws Exception {
     if (string.equals("abc") && number == 1) {
       throw new SkipThisScenarioException();
     }
   }
 
-  public int timeSomething(int reps) {
+  @Benchmark int something(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
       dummy += i;
@@ -56,7 +57,7 @@
     return dummy;
   }
 
-  public int timeSomethingElse(int reps) {
+  @Benchmark int somethingElse(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
       dummy -= i;
@@ -64,11 +65,7 @@
     return dummy;
   }
 
-  @Override protected void tearDown() throws Exception {
+  @AfterExperiment 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
index 291ec84..d28f9b0 100644
--- a/examples/src/main/java/examples/DoubleToStringBenchmark.java
+++ b/examples/src/main/java/examples/DoubleToStringBenchmark.java
@@ -16,17 +16,13 @@
 
 package examples;
 
+import com.google.caliper.Benchmark;
 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 {
+public class DoubleToStringBenchmark {
   @Param Method method;
 
   public enum Method {
@@ -83,7 +79,7 @@
 
   @Param Value value;
 
-  public int timePrimitive(int reps) {
+  @Benchmark int primitive(int reps) {
     double d = value.value;
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
@@ -92,7 +88,7 @@
     return dummy;
   }
 
-  public int timeWrapper(int reps) {
+  @Benchmark int wrapper(int reps) {
     Double d = value.value;
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
@@ -100,8 +96,4 @@
     }
     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
index 8e6c69a..3a6da5b 100644
--- a/examples/src/main/java/examples/DoubleToStringBenchmark2.java
+++ b/examples/src/main/java/examples/DoubleToStringBenchmark2.java
@@ -16,14 +16,13 @@
 
 package examples;
 
+import com.google.caliper.Benchmark;
 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 {
+public class DoubleToStringBenchmark2 {
   @Param boolean useWrapper;
 
   enum Value {
@@ -41,7 +40,7 @@
 
   @Param Value value;
 
-  public int timeToString(int reps) {
+  @Benchmark int toString(int reps) {
     int dummy = 0;
     if (useWrapper) {
       Double d = value.d;
@@ -57,7 +56,7 @@
     return dummy;
   }
 
-  public int timeStringValueOf(int reps) {
+  @Benchmark int stringValueOf(int reps) {
     int dummy = 0;
     if (useWrapper) {
       Double d = value.d;
@@ -73,7 +72,7 @@
     return dummy;
   }
 
-  public int timeStringFormat(int reps) {
+  @Benchmark int stringFormat(int reps) {
     int dummy = 0;
     if (useWrapper) {
       Double d = value.d;
@@ -89,7 +88,7 @@
     return dummy;
   }
 
-  public int timeQuoteTrick(int reps) {
+  @Benchmark int quoteTrick(int reps) {
     int dummy = 0;
     if (useWrapper) {
       Double d = value.d;
@@ -104,8 +103,4 @@
     }
     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
index b232514..10660a9 100644
--- a/examples/src/main/java/examples/EnumSetContainsBenchmark.java
+++ b/examples/src/main/java/examples/EnumSetContainsBenchmark.java
@@ -16,16 +16,17 @@
 
 package examples;
 
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
 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 {
+public class EnumSetContainsBenchmark {
 
   @Param private SetMaker setMaker;
 
@@ -75,18 +76,14 @@
   private Set<?> set;
   private Object[] testValues;
 
-  @Override protected void setUp() {
+  @BeforeExperiment void setUp() {
     this.set = setMaker.newSet();
     this.testValues = setMaker.testValues();
   }
 
-  public void timeContains(int reps) {
+  @Benchmark void contains(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
index a11b1bd..7c4bc79 100644
--- a/examples/src/main/java/examples/ExpensiveObjectsBenchmark.java
+++ b/examples/src/main/java/examples/ExpensiveObjectsBenchmark.java
@@ -16,9 +16,7 @@
 
 package examples;
 
-import com.google.caliper.SimpleBenchmark;
-import com.google.caliper.runner.CaliperMain;
-
+import com.google.caliper.Benchmark;
 import java.text.DecimalFormatSymbols;
 import java.text.NumberFormat;
 import java.text.SimpleDateFormat;
@@ -28,49 +26,43 @@
  * Benchmarks creation and cloning various expensive objects.
  */
 @SuppressWarnings({"ResultOfObjectAllocationIgnored"}) // TODO: should fix!
-public class ExpensiveObjectsBenchmark extends SimpleBenchmark {
-  public void timeNewDecimalFormatSymbols(int reps) {
+public class ExpensiveObjectsBenchmark {
+  @Benchmark void newDecimalFormatSymbols(int reps) {
     for (int i = 0; i < reps; ++i) {
       new DecimalFormatSymbols(Locale.US);
     }
   }
 
-  public void timeClonedDecimalFormatSymbols(int reps) {
+  @Benchmark void clonedDecimalFormatSymbols(int reps) {
     DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
     for (int i = 0; i < reps; ++i) {
       dfs.clone();
     }
   }
 
-  public void timeNewNumberFormat(int reps) {
+  @Benchmark void newNumberFormat(int reps) {
     for (int i = 0; i < reps; ++i) {
       NumberFormat.getInstance(Locale.US);
     }
   }
 
-  public void timeClonedNumberFormat(int reps) {
+  @Benchmark void clonedNumberFormat(int reps) {
     NumberFormat nf = NumberFormat.getInstance(Locale.US);
     for (int i = 0; i < reps; ++i) {
       nf.clone();
     }
   }
 
-  public void timeNewSimpleDateFormat(int reps) {
+  @Benchmark void newSimpleDateFormat(int reps) {
     for (int i = 0; i < reps; ++i) {
       new SimpleDateFormat();
     }
   }
 
-  public void timeClonedSimpleDateFormat(int reps) {
+  @Benchmark void clonedSimpleDateFormat(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
index b4a0541..93ed936 100644
--- a/examples/src/main/java/examples/FormatterBenchmark.java
+++ b/examples/src/main/java/examples/FormatterBenchmark.java
@@ -16,36 +16,37 @@
 
 package examples;
 
-import com.google.caliper.Runner;
-import com.google.caliper.SimpleBenchmark;
+import com.google.caliper.Benchmark;
 import java.util.Formatter;
 
 /**
  * Compares Formatter against hand-written StringBuilder code.
  */
-public class FormatterBenchmark extends SimpleBenchmark {
-  public void timeFormatter_NoFormatting(int reps) {
+public class FormatterBenchmark {
+  @Benchmark void formatter_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");
+      f.close();
     }
   }
 
-  public void timeStringBuilder_NoFormatting(int reps) {
+  @Benchmark void stringBuilder_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) {
+  @Benchmark void formatter_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);
+      f.close();
     }
   }
 
-  public void timeStringBuilder_OneInt(int reps) {
+  @Benchmark void stringBuilder_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 ");
@@ -54,14 +55,15 @@
     }
   }
 
-  public void timeFormatter_OneString(int reps) {
+  @Benchmark void formatter_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");
+      f.close();
     }
   }
 
-  public void timeStringBuilder_OneString(int reps) {
+  @Benchmark void stringBuilder_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 ");
@@ -69,8 +71,4 @@
       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
index 55a119c..bed415f 100644
--- a/examples/src/main/java/examples/IntModBenchmark.java
+++ b/examples/src/main/java/examples/IntModBenchmark.java
@@ -16,17 +16,16 @@
 
 package examples;
 
-import com.google.caliper.Runner;
-import com.google.caliper.SimpleBenchmark;
+import com.google.caliper.Benchmark;
 
 /**
  * Measures several candidate implementations for mod().
  */
 @SuppressWarnings("SameParameterValue")
-public class IntModBenchmark extends SimpleBenchmark {
+public class IntModBenchmark {
   private static final int M = (1 << 16) - 1;
 
-  public int timeConditional(int reps) {
+  @Benchmark int conditional(int reps) {
     int dummy = 5;
     for (int i = 0; i < reps; i++) {
       dummy += Integer.MAX_VALUE + conditionalMod(dummy, M);
@@ -39,7 +38,7 @@
     return r < 0 ? r + m : r;
   }
 
-  public int timeDoubleRemainder(int reps) {
+  @Benchmark int doubleRemainder(int reps) {
     int dummy = 5;
     for (int i = 0; i < reps; i++) {
       dummy += Integer.MAX_VALUE + doubleRemainderMod(dummy, M);
@@ -52,7 +51,7 @@
     return (int) ((a % m + (long) m) % m);
   }
 
-  public int timeRightShiftingMod(int reps) {
+  @Benchmark int rightShiftingMod(int reps) {
     int dummy = 5;
     for (int i = 0; i < reps; i++) {
       dummy += Integer.MAX_VALUE + rightShiftingMod(dummy, M);
@@ -66,7 +65,7 @@
      return (int) (r + (r >> 63 & m));
   }
 
-  public int timeLeftShiftingMod(int reps) {
+  @Benchmark int leftShiftingMod(int reps) {
     int dummy = 5;
     for (int i = 0; i < reps; i++) {
       dummy += Integer.MAX_VALUE + leftShiftingMod(dummy, M);
@@ -79,16 +78,11 @@
     return (int) ((a + ((long) m << 32)) % m);
   }
 
-  public int timeWrongMod(int reps) {
+  @Benchmark int wrongMod(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
index a8cfb05..07ae8eb 100644
--- a/examples/src/main/java/examples/ListIterationBenchmark.java
+++ b/examples/src/main/java/examples/ListIterationBenchmark.java
@@ -16,16 +16,17 @@
 
 package examples;
 
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
 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 {
+public class ListIterationBenchmark {
 
   @Param({"0", "10", "100", "1000"})
   private int length;
@@ -33,7 +34,7 @@
   private List<Object> list;
   private Object[] array;
 
-  @Override protected void setUp() {
+  @BeforeExperiment void setUp() {
     array = new Object[length];
     for (int i = 0; i < length; i++) {
       array[i] = new Object();
@@ -50,24 +51,23 @@
     };
   }
 
-  @SuppressWarnings({"UnusedDeclaration"}) // TODO: fix
-  public void timeListIteration(int reps) {
+  @Benchmark int listIteration(int reps) {
+    int dummy = 0;
     for (int i = 0; i < reps; i++) {
       for (Object value : list) {
+        dummy |= value.hashCode();
       }
     }
+    return dummy;
   }
 
-  @SuppressWarnings({"UnusedDeclaration"}) // TODO: fix
-  public void timeArrayIteration(int reps) {
+  @Benchmark int arrayIteration(int reps) {
+    int dummy = 0;
     for (int i = 0; i < reps; i++) {
       for (Object value : array) {
+        dummy |= value.hashCode();
       }
     }
-  }
-
-  // TODO: remove this from all examples when IDE plugins are ready
-  public static void main(String[] args) throws Exception {
-    Runner.main(ListIterationBenchmark.class, args);
+    return dummy;
   }
 }
\ No newline at end of file
diff --git a/examples/src/main/java/examples/ListModificationBenchmark.java b/examples/src/main/java/examples/ListModificationBenchmark.java
new file mode 100644
index 0000000..81d14f9
--- /dev/null
+++ b/examples/src/main/java/examples/ListModificationBenchmark.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2012 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.BeforeExperiment;
+import com.google.caliper.Benchmark;
+import com.google.caliper.Param;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Measures performance of list operations.
+ */
+public class ListModificationBenchmark {
+
+  private enum Element {
+    INSTANCE,
+  }
+  private enum ListImpl {
+    Array {
+      @Override List<Element> create() {
+        return new ArrayList<Element>();
+      }
+    },
+    Linked {
+      @Override List<Element> create() {
+        return new LinkedList<Element>();
+      }
+    };
+
+    abstract List<Element> create();
+  }
+
+  @Param({"10", "100", "1000", "10000"})
+  private int size;
+
+  @Param({"Array", "Linked"})
+  private ListImpl implementation;
+
+  private List<Element> list;
+
+  @BeforeExperiment void setUp() throws Exception {
+    list = implementation.create();
+    for (int i = 0; i < size; i++) {
+      list.add(Element.INSTANCE);
+    }
+  }
+
+  @Benchmark void populate(int reps) throws Exception {
+    for (int rep = 0; rep < reps; rep++) {
+      List<Element> list = implementation.create();
+      for (int i = 0; i < size; i++) {
+        list.add(Element.INSTANCE);
+      }
+    }
+  }
+
+  @Benchmark void iteration(int reps) {
+    for (int rep = 0; rep < reps; rep++) {
+      Iterator<Element> iterator = list.iterator();
+      while (iterator.hasNext()) {
+        iterator.next();
+      }
+    }
+  }
+
+  @Benchmark void headAddRemove(int reps) {
+    for (int rep = 0; rep < reps; rep++) {
+      list.add(0, Element.INSTANCE);
+      list.remove(0);
+    }
+  }
+
+  @Benchmark void middleAddRemove(int reps) {
+    int index = size / 2;
+    for (int rep = 0; rep < reps; rep++) {
+      list.add(index, Element.INSTANCE);
+      list.remove(index);
+    }
+  }
+
+  @Benchmark void tailAddRemove(int reps) {
+    int index = size - 1;
+    for (int rep = 0; rep < reps; rep++) {
+      list.add(Element.INSTANCE);
+      list.remove(index);
+    }
+  }
+}
diff --git a/examples/src/main/java/examples/LoopingBackwardsBenchmark.java b/examples/src/main/java/examples/LoopingBackwardsBenchmark.java
index 1e3d1ad..d23a28d 100644
--- a/examples/src/main/java/examples/LoopingBackwardsBenchmark.java
+++ b/examples/src/main/java/examples/LoopingBackwardsBenchmark.java
@@ -16,17 +16,16 @@
 
 package examples;
 
+import com.google.caliper.Benchmark;
 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 {
+public class LoopingBackwardsBenchmark {
   @Param({"2", "20", "2000", "20000000"}) int max;
 
-  public int timeForwards(int reps) {
+  @Benchmark int forwards(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
       for (int j = 0; j < max; j++) {
@@ -36,7 +35,7 @@
     return dummy;
   }
 
-  public int timeBackwards(int reps) {
+  @Benchmark int backwards(int reps) {
     int dummy = 0;
     for (int i = 0; i < reps; i++) {
       for (int j = max - 1; j >= 0; j--) {
@@ -45,8 +44,4 @@
     }
     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
index c9437bd..681d10e 100644
--- a/examples/src/main/java/examples/MessageDigestCreationBenchmark.java
+++ b/examples/src/main/java/examples/MessageDigestCreationBenchmark.java
@@ -16,29 +16,24 @@
 
 package examples;
 
+import com.google.caliper.Benchmark;
 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 {
+public class MessageDigestCreationBenchmark {
   // 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 {
+  @Benchmark 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/caliper/src/main/java/com/google/caliper/UploadResults.java b/examples/src/main/java/examples/NoOpBenchmark.java
similarity index 63%
copy from caliper/src/main/java/com/google/caliper/UploadResults.java
copy to examples/src/main/java/examples/NoOpBenchmark.java
index 7dae7af..fa24805 100644
--- a/caliper/src/main/java/com/google/caliper/UploadResults.java
+++ b/examples/src/main/java/examples/NoOpBenchmark.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2013 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-package com.google.caliper;
+package examples;
 
-import java.io.File;
+import com.google.caliper.Benchmark;
 
 /**
- * Usage: UploadResults <file_or_dir>
+ * This is the absolute minimal benchmark. It does nothing but time the rep loop.
  */
-public class UploadResults {
-  public static void main(String[] args) {
-    new Runner().uploadResultsFileOrDir(new File(args[0]));
+public class NoOpBenchmark {
+  @Benchmark long increment(long reps) {
+    long result = 0;
+    for (; result < reps; result++) {}
+    return result;
   }
 }
diff --git a/examples/src/main/java/examples/StringBuilderBenchmark.java b/examples/src/main/java/examples/StringBuilderBenchmark.java
index 3c839d3..c39b3be 100644
--- a/examples/src/main/java/examples/StringBuilderBenchmark.java
+++ b/examples/src/main/java/examples/StringBuilderBenchmark.java
@@ -16,117 +16,176 @@
 
 package examples;
 
+import static java.lang.Character.MIN_SURROGATE;
+
+import com.google.caliper.Benchmark;
 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 {
+public class StringBuilderBenchmark {
 
-    @Param({"1", "10", "100"}) private int length;
+  @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);
-            }
+  @Benchmark void appendBoolean(int reps) {
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < reps; ++i) {
+      sb.setLength(0);
+      for (int j = 0; j < length; ++j) {
+        sb.append(true);
+        sb.append(false);
+      }
+    }
+  }
+
+  @Benchmark void appendChar(int reps) {
+    for (int i = 0; i < reps; ++i) {
+      StringBuilder sb = new StringBuilder();
+      for (int j = 0; j < length; ++j) {
+        sb.append('c');
+      }
+    }
+  }
+
+  @Benchmark void appendCharArray(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);
+      }
+    }
+  }
+
+  @Benchmark void appendCharSequence(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);
+      }
+    }
+  }
+
+  @Benchmark void appendDouble(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);
+      }
+    }
+  }
+
+  @Benchmark void appendFloat(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);
+      }
+    }
+  }
+
+  @Benchmark void appendInt(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);
+      }
+    }
+  }
+
+  @Benchmark void appendLong(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);
+      }
+    }
+  }
+
+  @Benchmark void appendObject(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);
+      }
+    }
+  }
+
+  @Benchmark void appendString(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);
+      }
+    }
+  }
+
+  @Benchmark void appendNull(int reps) {
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < reps; ++i) {
+      sb.setLength(0);
+      for (int j = 0; j < length; ++j) {
+        sb.append((String)null);
+        sb.append((StringBuilder)null);
+      }
+    }
+  }
+
+  /** Times .reverse() when no surrogates are present. */
+  @Benchmark void reverseNoSurrogates(int reps) {
+    final int length = Math.min(this.length, MIN_SURROGATE);
+    StringBuilder sb = new StringBuilder();
+    for (int j = 0; j < length; j++) {
+      sb.appendCodePoint(j);
+    }
+    for (int i = 0; i < reps; i++) {
+      for (int j = 0; j < 4; j++) {
+        sb.reverse();
+      }
+      if (sb.codePointAt(0) > MIN_SURROGATE)
+        throw new Error();
+    }
+  }
+
+  /** Times .codePointAt(int) when no surrogates are present. */
+  @Benchmark void codePointAtNoSurrogates(int reps) {
+    final int length = Math.min(this.length, MIN_SURROGATE);
+    StringBuilder sb = new StringBuilder();
+    for (int j = 0; j < length; j++) {
+      sb.appendCodePoint(j);
+    }
+    for (int i = 0; i < reps; i++) {
+      for (int j = 0; j < 4; j++) {
+        for (int k = 0; k < length - 1; k++) {
+          if (sb.codePointAt(k) > MIN_SURROGATE)
+            throw new Error();
         }
+      }
     }
+  }
 
-    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');
-            }
+  /** Times .codePointBefore(int) when no surrogates are present. */
+  @Benchmark void codePointBeforeNoSurrogates(int reps) {
+    final int length = Math.min(this.length, MIN_SURROGATE);
+    StringBuilder sb = new StringBuilder();
+    for (int j = 0; j < length; j++) {
+      sb.appendCodePoint(j);
+    }
+    for (int i = 0; i < reps; i++) {
+      for (int j = 0; j < 4; j++) {
+        for (int k = 1; k < length; k++) {
+          if (sb.codePointBefore(k) > MIN_SURROGATE)
+            throw new Error();
         }
+      }
     }
-
-    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/examples/src/main/java/examples/Utf8Benchmark.java b/examples/src/main/java/examples/Utf8Benchmark.java
new file mode 100644
index 0000000..97b4e00
--- /dev/null
+++ b/examples/src/main/java/examples/Utf8Benchmark.java
@@ -0,0 +1,141 @@
+/*
+ * 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.BeforeExperiment;
+import com.google.caliper.Benchmark;
+import com.google.caliper.Param;
+
+import java.nio.charset.Charset;
+import java.util.Random;
+
+/**
+ * Benchmark for operations with the UTF-8 charset.
+ */
+public class Utf8Benchmark {
+
+  static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  /**
+   * The maximum code point used in generated text.  Different values
+   * provide reasonable models of different real-world human text.
+   */
+  static class MaxCodePoint {
+    final int value;
+
+    /**
+     * Convert the input string to a code point.  Accepts regular
+     * decimal numerals, hex strings, and some symbolic names
+     * meaningful to humans.
+     */
+    private static int decode(String userFriendly) {
+      try {
+        return Integer.decode(userFriendly);
+      } catch (NumberFormatException ignored) {
+        if (userFriendly.matches("(?i)(?:American|English|ASCII)")) {
+          // 1-byte UTF-8 sequences - "American" ASCII text
+          return 0x80;
+        } else if (userFriendly.matches("(?i)(?:French|Latin|Western.*European)")) {
+          // Mostly 1-byte UTF-8 sequences, mixed with occasional 2-byte
+          // sequences - "Western European" text
+          return 0x90;
+        } else if (userFriendly.matches("(?i)(?:Branch.*Prediction.*Hostile)")) {
+          // Defeat branch predictor for: c < 0x80 ; branch taken 50% of the time.
+          return 0x100;
+        } else if (userFriendly.matches("(?i)(?:Greek|Cyrillic|European|ISO.?8859)")) {
+          // Mostly 2-byte UTF-8 sequences - "European" text
+          return 0x800;
+        } else if (userFriendly.matches("(?i)(?:Chinese|Han|Asian|BMP)")) {
+          // Mostly 3-byte UTF-8 sequences - "Asian" text
+          return Character.MIN_SUPPLEMENTARY_CODE_POINT;
+        } else if (userFriendly.matches("(?i)(?:Cuneiform|rare|exotic|supplementary.*)")) {
+          // Mostly 4-byte UTF-8 sequences - "rare exotic" text
+          return Character.MAX_CODE_POINT;
+        } else {
+          throw new IllegalArgumentException("Can't decode codepoint " + userFriendly);
+        }
+      }
+    }
+
+    public static MaxCodePoint valueOf(String userFriendly) {
+      return new MaxCodePoint(userFriendly);
+    }
+
+    private MaxCodePoint(String userFriendly) {
+      value = decode(userFriendly);
+    }
+  }
+
+  /**
+   * The default values of maxCodePoint below provide pretty good
+   * performance models of different kinds of common human text.
+   * @see MaxCodePoint#decode
+   */
+  @Param({"0x80", "0x100", "0x800", "0x10000", "0x10ffff"}) MaxCodePoint maxCodePoint;
+
+  static final int STRING_COUNT = 1 << 7;
+
+  @Param({"65536"}) int charCount;
+  private String[] strings;
+
+  /**
+   * Computes arrays of valid unicode Strings.
+   */
+  @BeforeExperiment void setUp() {
+    final long seed = 99;
+    final Random rnd = new Random(seed);
+    strings = new String[STRING_COUNT];
+    for (int i = 0; i < STRING_COUNT; i++) {
+      StringBuilder sb = new StringBuilder();
+      for (int j = 0; j < charCount; j++) {
+        int codePoint;
+        // discard illegal surrogate "codepoints"
+        do {
+          codePoint = rnd.nextInt(maxCodePoint.value);
+        } while (isSurrogate(codePoint));
+        sb.appendCodePoint(codePoint);
+      }
+      strings[i] = sb.toString();
+    }
+    // The reps will continue until the non-determinism detector is pacified!
+    getBytes(100);
+  }
+
+  /**
+   * Benchmarks {@link String#getBytes} on valid strings containing
+   * pseudo-randomly-generated codePoints less than {@code
+   * maxCodePoint}.  A constant seed is used, so separate runs perform
+   * identical computations.
+   */
+  @Benchmark void getBytes(int reps) {
+    final String[] strings = this.strings;
+    final int mask = STRING_COUNT - 1;
+    for (int i = 0; i < reps; i++) {
+      String string = strings[i & mask];
+      byte[] bytes = string.getBytes(UTF_8);
+      if (bytes[0] == 86 && bytes[bytes.length - 1] == 99) {
+        throw new Error("Unlikely! We're just defeating the optimizer!");
+      }
+    }
+  }
+
+  /** Character.isSurrogate was added in Java SE 7. */
+  private boolean isSurrogate(int c) {
+    return (Character.MIN_HIGH_SURROGATE <= c &&
+            c <= Character.MAX_LOW_SURROGATE);
+  }
+}
diff --git a/examples/src/main/java/examples/VarargsBenchmark.java b/examples/src/main/java/examples/VarargsBenchmark.java
new file mode 100644
index 0000000..eceb72d
--- /dev/null
+++ b/examples/src/main/java/examples/VarargsBenchmark.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2012 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.BeforeExperiment;
+import com.google.caliper.Benchmark;
+import com.google.caliper.Param;
+import com.google.caliper.api.SkipThisScenarioException;
+
+import java.util.Random;
+
+/**
+ * Benchmarks the overhead created by using varargs instead of parameter expansion.
+ *
+ * @author gak@google.com (Gregory Kick)
+ */
+public final class VarargsBenchmark {
+  enum Strategy {
+    VARARGS {
+      @Override long one(long a) {
+        return varargs(a);
+      }
+
+      @Override long two(long a, long b) {
+        return varargs(a, b);
+      }
+
+      @Override long three(long a, long b, long c) {
+        return varargs(a, b, c);
+      }
+
+      @Override long four(long a, long b, long c, long d) {
+        return varargs(a, b, c, d);
+      }
+
+      @Override long five(long a, long b, long c, long d, long e) {
+        return varargs(a, b, c, d);
+      }
+
+      @Override long six(long a, long b, long c, long d, long e, long f) {
+        return varargs(a, b, c, d, e, f);
+      }},
+    EXPANSION {
+      @Override long one(long a) {
+        return VarargsBenchmark.one(a);
+      }
+
+      @Override long two(long a, long b) {
+        return VarargsBenchmark.two(a, b);
+      }
+
+      @Override long three(long a, long b, long c) {
+        return VarargsBenchmark.three(a, b, c);
+      }
+
+      @Override long four(long a, long b, long c, long d) {
+        return VarargsBenchmark.four(a, b, c, d);
+      }
+
+      @Override long five(long a, long b, long c, long d, long e) {
+        return VarargsBenchmark.five(a, b, c, d, e);
+      }
+
+      @Override long six(long a, long b, long c, long d, long e, long f) {
+         return VarargsBenchmark.six(a, b, c, d, e, f);
+      }
+    };
+
+    abstract long one(long a);
+
+    abstract long two(long a, long b);
+
+    abstract long three(long a, long b, long c);
+
+    abstract long four(long a, long b, long c, long d);
+
+    abstract long five(long a, long b, long c, long d, long e);
+
+    abstract long six(long a, long b, long c, long d, long e, long f);
+  }
+
+  private static long varargs(long... longs) {
+    long result = 0;
+    for (long i : longs) {
+      result ^= i;
+    }
+    return result;
+  }
+
+  private static long one(long a) {
+    return a;
+  }
+
+  private static long two(long a, long b) {
+    return a ^ b;
+  }
+
+  private static long three(long a, long b, long c) {
+    return a ^ b ^ c;
+  }
+
+  private static long four(long a, long b, long c, long d) {
+    return a ^ b ^ c ^ d;
+  }
+
+  private static long five(long a, long b, long c, long d, long e) {
+    return a ^ b ^ c ^ d ^ e;
+  }
+
+  private static long six(long a, long b, long c, long d, long e, long f) {
+    return a ^ b ^ c ^ d ^ e ^ f;
+  }
+
+  @Param private Strategy strategy;
+  @Param({"1", "2", "3", "4", "5", "6"}) private int arguments;
+
+  private long[] data = new long[2048];
+
+  @BeforeExperiment void setUp() {
+    Random random = new Random();
+    for (int i = 0; i < data.length; i++) {
+      data[i] = random.nextLong();
+    }
+  }
+
+  @Benchmark long invocation(int reps) {
+    switch (arguments) {
+      case 1:
+        return oneArgument(reps);
+      case 2:
+        return twoArguments(reps);
+      case 3:
+        return threeArguments(reps);
+      case 4:
+        return fourArguments(reps);
+      case 5:
+        return fiveArguments(reps);
+      case 6:
+        return sixArguments(reps);
+      default:
+        throw new SkipThisScenarioException();
+    }
+  }
+
+  private long oneArgument(int reps) {
+    long dummy = 0;
+    long[] data = this.data;
+    int dataLength = data.length;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.one(data[i % dataLength]);
+    }
+    return dummy;
+  }
+
+  private long twoArguments(int reps) {
+    long dummy = 0;
+    long[] data = this.data;
+    int dataLength = data.length;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.two(data[i % dataLength], data[(i + 1) % dataLength]);
+    }
+    return dummy;
+  }
+
+  private long threeArguments(int reps) {
+    long dummy = 0;
+    long[] data = this.data;
+    int dataLength = data.length;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.three(
+          data[i % dataLength],
+          data[(i + 1) % dataLength],
+          data[(i + 2) % dataLength]);
+    }
+    return dummy;
+  }
+
+  private long fourArguments(int reps) {
+    long dummy = 0;
+    long[] data = this.data;
+    int dataLength = data.length;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.four(
+          data[i % dataLength],
+          data[(i + 1) % dataLength],
+          data[(i + 2) % dataLength],
+          data[(i + 3) % dataLength]);
+    }
+    return dummy;
+  }
+
+  private long fiveArguments(int reps) {
+    long dummy = 0;
+    long[] data = this.data;
+    int dataLength = data.length;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.five(
+          data[i % dataLength],
+          data[(i + 1) % dataLength],
+          data[(i + 2) % dataLength],
+          data[(i + 3) % dataLength],
+          data[(i + 4) % dataLength]);
+    }
+    return dummy;
+  }
+
+  private long sixArguments(int reps) {
+    long dummy = 0;
+    long[] data = this.data;
+    int dataLength = data.length;
+    for (int i = 0; i < reps; i++) {
+      dummy += strategy.six(
+          data[i % dataLength],
+          data[(i + 1) % dataLength],
+          data[(i + 2) % dataLength],
+          data[(i + 3) % dataLength],
+          data[(i + 4) % dataLength],
+          data[(i + 5) % dataLength]);
+    }
+    return dummy;
+  }
+}
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
new file mode 100644
index 0000000..8caadbe
--- /dev/null
+++ b/expectations/knownfailures.txt
@@ -0,0 +1,76 @@
+[
+{
+  description: "ObjectGraphMeasurer doesn't work on Android",
+  names: [
+    "com.google.caliper.memory.ObjectGraphMeasurerTest"
+  ]
+},
+{
+  description: "Ljava/lang/management/ManagementFactory; doesn't exist on Android",
+  names: [
+    "com.google.caliper.config.CaliperConfigTest#getDefaultVmConfig",
+    "com.google.caliper.runner.StreamServiceTest",
+    "com.google.caliper.runner.WorkerProcessTest"
+  ]
+},
+{
+  description: "@RunWith(MockitoJUnitRunner) doesn't work in Vogar",
+  names: [
+    "com.google.caliper.config.CaliperConfigLoaderTest",
+    "com.google.caliper.config.LoggingConfigLoaderTest",
+    "com.google.caliper.runner.ExperimentingRunnerModuleTest"
+  ]
+},
+{
+  description: "Assumes it is running on a standard Java platform with java binary",
+  names: [
+    "com.google.caliper.config.VmConfigTest#testExecutable"
+  ]
+},
+{
+  description: "AllocationInstrument doesn't work on Android",
+  names: [
+    "com.google.caliper.runner.AllocationInstrumentTest",
+    "com.google.caliper.runner.BadUserCodeTest#testComplexNonDeterministicAllocation_noTrackAllocations",
+    "com.google.caliper.runner.BadUserCodeTest#testComplexNonDeterministicAllocation_trackAllocations",
+    "com.google.caliper.runner.BadUserCodeTest#testNonDeterministicAllocation_noTrackAllocations",
+    "com.google.caliper.runner.BadUserCodeTest#testNonDeterministicAllocation_trackAllocations",
+    "com.google.caliper.runner.MalformedBenchmarksTest#noBenchmarkMethods"
+  ]
+},
+{
+  description: "ArbitraryMeasurementInstrument doesn't work on Android",
+  names: [
+    "com.google.caliper.runner.ArbitraryMeasurmentInstrumentTest"
+  ]
+},
+{
+  description: "Android only has 256M heap",
+  names: [
+    "com.google.caliper.runner.RuntimeInstrumentTest#gcBeforeEachOptionIsHonored",
+    "com.google.caliper.runner.RuntimeInstrumentTest#gcBeforeEachOptionIsReallyNecessary"
+  ]
+},
+{
+  description: "Checks for a specific exception message that is an implementation detail of JVM",
+  names: [
+    "com.google.caliper.runner.MalformedBenchmarksTest#unparsableParamDefault"
+  ]
+},
+{
+  description: "Unknown cause",
+  names: [
+    "com.google.caliper.runner.ServerSocketServiceTest#getConnectionStoppedService",
+    "com.google.caliper.runner.BadUserCodeTest#testExceptionInMethod_notInDryRun",#
+    "com.google.caliper.runner.RuntimeInstrumentTest#maxWarmupWallTimeOptionIsHonored"
+  ]
+},
+{
+  description: "Possible race in parse code that causes dryRun to be true when it is false, goes away under debug",
+  names: [
+    "com.google.caliper.options.ParsedOptionsTest#testDefaults_DoNotRequireBenchmarkClassName",
+    "com.google.caliper.options.ParsedOptionsTest#testDefaults_RequireBenchmarkClassName",
+    "com.google.caliper.options.ParsedOptionsTest#testKitchenSink"
+  ]
+}
+]
diff --git a/lib/gson-1.7.1.jar b/lib/gson-1.7.1.jar
deleted file mode 100644
index eb54274..0000000
--- a/lib/gson-1.7.1.jar
+++ /dev/null
Binary files differ
diff --git a/lib/gson-2.2.2-sources.jar b/lib/gson-2.2.2-sources.jar
new file mode 100644
index 0000000..26c43d2
--- /dev/null
+++ b/lib/gson-2.2.2-sources.jar
Binary files differ
diff --git a/lib/gson-2.2.2.jar b/lib/gson-2.2.2.jar
new file mode 100644
index 0000000..9adc66f
--- /dev/null
+++ b/lib/gson-2.2.2.jar
Binary files differ
diff --git a/lib/gson-1.7.1.jar.txt b/lib/gson-2.2.2.jar.txt
similarity index 100%
rename from lib/gson-1.7.1.jar.txt
rename to lib/gson-2.2.2.jar.txt
diff --git a/lib/java-allocation-instrumenter-2.0-sources.jar b/lib/java-allocation-instrumenter-2.0-sources.jar
new file mode 100644
index 0000000..d8cc489
--- /dev/null
+++ b/lib/java-allocation-instrumenter-2.0-sources.jar
Binary files differ
diff --git a/lib/jersey-client-1.11-sources.jar b/lib/jersey-client-1.11-sources.jar
new file mode 100644
index 0000000..58164ca
--- /dev/null
+++ b/lib/jersey-client-1.11-sources.jar
Binary files differ
diff --git a/lib/jersey-client-1.11.jar b/lib/jersey-client-1.11.jar
new file mode 100644
index 0000000..42d8925
--- /dev/null
+++ b/lib/jersey-client-1.11.jar
Binary files differ
diff --git a/lib/jersey-client-1.11.jar.txt b/lib/jersey-client-1.11.jar.txt
new file mode 100644
index 0000000..8681d13
--- /dev/null
+++ b/lib/jersey-client-1.11.jar.txt
@@ -0,0 +1,712 @@
+COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1
+
+1. Definitions.
+
+    1.1. "Contributor" means each individual or entity that creates or
+    contributes to the creation of Modifications.
+
+    1.2. "Contributor Version" means the combination of the Original
+    Software, prior Modifications used by a Contributor (if any), and the
+    Modifications made by that particular Contributor.
+
+    1.3. "Covered Software" means (a) the Original Software, or (b)
+    Modifications, or (c) the combination of files containing Original
+    Software with files containing Modifications, in each case including
+    portions thereof.
+
+    1.4. "Executable" means the Covered Software in any form other than
+    Source Code.
+
+    1.5. "Initial Developer" means the individual or entity that first makes
+    Original Software available under this License.
+
+    1.6. "Larger Work" means a work which combines Covered Software or
+    portions thereof with code not governed by the terms of this License.
+
+    1.7. "License" means this document.
+
+    1.8. "Licensable" means having the right to grant, to the maximum extent
+    possible, whether at the time of the initial grant or subsequently
+    acquired, any and all of the rights conveyed herein.
+
+    1.9. "Modifications" means the Source Code and Executable form of any of
+    the following:
+
+    A. Any file that results from an addition to, deletion from or
+    modification of the contents of a file containing Original Software or
+    previous Modifications;
+
+    B. Any new file that contains any part of the Original Software or
+    previous Modification; or
+
+    C. Any new file that is contributed or otherwise made available under
+    the terms of this License.
+
+    1.10. "Original Software" means the Source Code and Executable form of
+    computer software code that is originally released under this License.
+
+    1.11. "Patent Claims" means any patent claim(s), now owned or hereafter
+    acquired, including without limitation, method, process, and apparatus
+    claims, in any patent Licensable by grantor.
+
+    1.12. "Source Code" means (a) the common form of computer software code
+    in which modifications are made and (b) associated documentation
+    included in or with such code.
+
+    1.13. "You" (or "Your") means an individual or a legal entity exercising
+    rights under, and complying with all of the terms of, this License. For
+    legal entities, "You" includes any entity which controls, is controlled
+    by, or is under common control with You. For purposes of this
+    definition, "control" means (a) the power, direct or indirect, to cause
+    the direction or management of such entity, whether by contract or
+    otherwise, or (b) ownership of more than fifty percent (50%) of the
+    outstanding shares or beneficial ownership of such entity.
+
+2. License Grants.
+
+    2.1. The Initial Developer Grant.
+
+    Conditioned upon Your compliance with Section 3.1 below and subject to
+    third party intellectual property claims, the Initial Developer hereby
+    grants You a world-wide, royalty-free, non-exclusive license:
+
+    (a) under intellectual property rights (other than patent or trademark)
+    Licensable by Initial Developer, to use, reproduce, modify, display,
+    perform, sublicense and distribute the Original Software (or portions
+    thereof), with or without Modifications, and/or as part of a Larger
+    Work; and
+
+    (b) under Patent Claims infringed by the making, using or selling of
+    Original Software, to make, have made, use, practice, sell, and offer
+    for sale, and/or otherwise dispose of the Original Software (or portions
+    thereof).
+
+    (c) The licenses granted in Sections 2.1(a) and (b) are effective on the
+    date Initial Developer first distributes or otherwise makes the Original
+    Software available to a third party under the terms of this License.
+
+    (d) Notwithstanding Section 2.1(b) above, no patent license is granted:
+    (1) for code that You delete from the Original Software, or (2) for
+    infringements caused by: (i) the modification of the Original Software,
+    or (ii) the combination of the Original Software with other software or
+    devices.
+
+    2.2. Contributor Grant.
+
+    Conditioned upon Your compliance with Section 3.1 below and subject to
+    third party intellectual property claims, each Contributor hereby grants
+    You a world-wide, royalty-free, non-exclusive license:
+
+    (a) under intellectual property rights (other than patent or trademark)
+    Licensable by Contributor to use, reproduce, modify, display, perform,
+    sublicense and distribute the Modifications created by such Contributor
+    (or portions thereof), either on an unmodified basis, with other
+    Modifications, as Covered Software and/or as part of a Larger Work; and
+
+    (b) under Patent Claims infringed by the making, using, or selling of
+    Modifications made by that Contributor either alone and/or in
+    combination with its Contributor Version (or portions of such
+    combination), to make, use, sell, offer for sale, have made, and/or
+    otherwise dispose of: (1) Modifications made by that Contributor (or
+    portions thereof); and (2) the combination of Modifications made by that
+    Contributor with its Contributor Version (or portions of such
+    combination).
+
+    (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on
+    the date Contributor first distributes or otherwise makes the
+    Modifications available to a third party.
+
+    (d) Notwithstanding Section 2.2(b) above, no patent license is granted:
+    (1) for any code that Contributor has deleted from the Contributor
+    Version; (2) for infringements caused by: (i) third party modifications
+    of Contributor Version, or (ii) the combination of Modifications made by
+    that Contributor with other software (except as part of the Contributor
+    Version) or other devices; or (3) under Patent Claims infringed by
+    Covered Software in the absence of Modifications made by that
+    Contributor.
+
+3. Distribution Obligations.
+
+    3.1. Availability of Source Code.
+
+    Any Covered Software that You distribute or otherwise make available in
+    Executable form must also be made available in Source Code form and that
+    Source Code form must be distributed only under the terms of this
+    License. You must include a copy of this License with every copy of the
+    Source Code form of the Covered Software You distribute or otherwise
+    make available. You must inform recipients of any such Covered Software
+    in Executable form as to how they can obtain such Covered Software in
+    Source Code form in a reasonable manner on or through a medium
+    customarily used for software exchange.
+
+    3.2. Modifications.
+
+    The Modifications that You create or to which You contribute are
+    governed by the terms of this License. You represent that You believe
+    Your Modifications are Your original creation(s) and/or You have
+    sufficient rights to grant the rights conveyed by this License.
+
+    3.3. Required Notices.
+
+    You must include a notice in each of Your Modifications that identifies
+    You as the Contributor of the Modification. You may not remove or alter
+    any copyright, patent or trademark notices contained within the Covered
+    Software, or any notices of licensing or any descriptive text giving
+    attribution to any Contributor or the Initial Developer.
+
+    3.4. Application of Additional Terms.
+
+    You may not offer or impose any terms on any Covered Software in Source
+    Code form that alters or restricts the applicable version of this
+    License or the recipients' rights hereunder. You may choose to offer,
+    and to charge a fee for, warranty, support, indemnity or liability
+    obligations to one or more recipients of Covered Software. However, you
+    may do so only on Your own behalf, and not on behalf of the Initial
+    Developer or any Contributor. You must make it absolutely clear that any
+    such warranty, support, indemnity or liability obligation is offered by
+    You alone, and You hereby agree to indemnify the Initial Developer and
+    every Contributor for any liability incurred by the Initial Developer or
+    such Contributor as a result of warranty, support, indemnity or
+    liability terms You offer.
+
+    3.5. Distribution of Executable Versions.
+
+    You may distribute the Executable form of the Covered Software under the
+    terms of this License or under the terms of a license of Your choice,
+    which may contain terms different from this License, provided that You
+    are in compliance with the terms of this License and that the license
+    for the Executable form does not attempt to limit or alter the
+    recipient's rights in the Source Code form from the rights set forth in
+    this License. If You distribute the Covered Software in Executable form
+    under a different license, You must make it absolutely clear that any
+    terms which differ from this License are offered by You alone, not by
+    the Initial Developer or Contributor. You hereby agree to indemnify the
+    Initial Developer and every Contributor for any liability incurred by
+    the Initial Developer or such Contributor as a result of any such terms
+    You offer.
+
+    3.6. Larger Works.
+
+    You may create a Larger Work by combining Covered Software with other
+    code not governed by the terms of this License and distribute the Larger
+    Work as a single product. In such a case, You must make sure the
+    requirements of this License are fulfilled for the Covered Software.
+
+4. Versions of the License.
+
+    4.1. New Versions.
+
+    Oracle is the initial license steward and may publish revised and/or new
+    versions of this License from time to time. Each version will be given a
+    distinguishing version number. Except as provided in Section 4.3, no one
+    other than the license steward has the right to modify this License.
+
+    4.2. Effect of New Versions.
+
+    You may always continue to use, distribute or otherwise make the Covered
+    Software available under the terms of the version of the License under
+    which You originally received the Covered Software. If the Initial
+    Developer includes a notice in the Original Software prohibiting it from
+    being distributed or otherwise made available under any subsequent
+    version of the License, You must distribute and make the Covered
+    Software available under the terms of the version of the License under
+    which You originally received the Covered Software. Otherwise, You may
+    also choose to use, distribute or otherwise make the Covered Software
+    available under the terms of any subsequent version of the License
+    published by the license steward.
+
+    4.3. Modified Versions.
+
+    When You are an Initial Developer and You want to create a new license
+    for Your Original Software, You may create and use a modified version of
+    this License if You: (a) rename the license and remove any references to
+    the name of the license steward (except to note that the license differs
+    from this License); and (b) otherwise make it clear that the license
+    contains terms which differ from this License.
+
+5. DISCLAIMER OF WARRANTY.
+
+    COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+    WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+    WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF
+    DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+    THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
+    SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY
+    RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME
+    THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS
+    DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO
+    USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
+    DISCLAIMER.
+
+6. TERMINATION.
+
+    6.1. This License and the rights granted hereunder will terminate
+    automatically if You fail to comply with terms herein and fail to cure
+    such breach within 30 days of becoming aware of the breach. Provisions
+    which, by their nature, must remain in effect beyond the termination of
+    this License shall survive.
+
+    6.2. If You assert a patent infringement claim (excluding declaratory
+    judgment actions) against Initial Developer or a Contributor (the
+    Initial Developer or Contributor against whom You assert such claim is
+    referred to as "Participant") alleging that the Participant Software
+    (meaning the Contributor Version where the Participant is a Contributor
+    or the Original Software where the Participant is the Initial Developer)
+    directly or indirectly infringes any patent, then any and all rights
+    granted directly or indirectly to You by such Participant, the Initial
+    Developer (if the Initial Developer is not the Participant) and all
+    Contributors under Sections 2.1 and/or 2.2 of this License shall, upon
+    60 days notice from Participant terminate prospectively and
+    automatically at the expiration of such 60 day notice period, unless if
+    within such 60 day period You withdraw Your claim with respect to the
+    Participant Software against such Participant either unilaterally or
+    pursuant to a written agreement with Participant.
+
+    6.3. If You assert a patent infringement claim against Participant
+    alleging that the Participant Software directly or indirectly infringes
+    any patent where such claim is resolved (such as by license or
+    settlement) prior to the initiation of patent infringement litigation,
+    then the reasonable value of the licenses granted by such Participant
+    under Sections 2.1 or 2.2 shall be taken into account in determining the
+    amount or value of any payment or license.
+
+    6.4. In the event of termination under Sections 6.1 or 6.2 above, all
+    end user licenses that have been validly granted by You or any
+    distributor hereunder prior to termination (excluding licenses granted
+    to You by any distributor) shall survive termination.
+
+7. LIMITATION OF LIABILITY.
+
+    UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+    (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+    DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED
+    SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY
+    PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+    OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF
+    GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL
+    OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+    INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+    LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+    RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+    PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
+    OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION
+    AND LIMITATION MAY NOT APPLY TO YOU.
+
+8. U.S. GOVERNMENT END USERS.
+
+    The Covered Software is a "commercial item," as that term is defined in
+    48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+    software" (as that term is defined at 48 C.F.R. § 252.227-7014(a)(1))
+    and "commercial computer software documentation" as such terms are used
+    in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and
+    48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government
+    End Users acquire Covered Software with only those rights set forth
+    herein. This U.S. Government Rights clause is in lieu of, and
+    supersedes, any other FAR, DFAR, or other clause or provision that
+    addresses Government rights in computer software under this License.
+
+9. MISCELLANEOUS.
+
+    This License represents the complete agreement concerning subject matter
+    hereof. If any provision of this License is held to be unenforceable,
+    such provision shall be reformed only to the extent necessary to make it
+    enforceable. This License shall be governed by the law of the
+    jurisdiction specified in a notice contained within the Original
+    Software (except to the extent applicable law, if any, provides
+    otherwise), excluding such jurisdiction's conflict-of-law provisions.
+    Any litigation relating to this License shall be subject to the
+    jurisdiction of the courts located in the jurisdiction and venue
+    specified in a notice contained within the Original Software, with the
+    losing party responsible for costs, including, without limitation, court
+    costs and reasonable attorneys' fees and expenses. The application of
+    the United Nations Convention on Contracts for the International Sale of
+    Goods is expressly excluded. Any law or regulation which provides that
+    the language of a contract shall be construed against the drafter shall
+    not apply to this License. You agree that You alone are responsible for
+    compliance with the United States export administration regulations (and
+    the export control laws and regulation of any other countries) when You
+    use, distribute or otherwise make available any Covered Software.
+
+10. RESPONSIBILITY FOR CLAIMS.
+
+    As between Initial Developer and the Contributors, each party is
+    responsible for claims and damages arising, directly or indirectly, out
+    of its utilization of rights under this License and You agree to work
+    with Initial Developer and Contributors to distribute such
+    responsibility on an equitable basis. Nothing herein is intended or
+    shall be deemed to constitute any admission of liability.
+
+NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION
+LICENSE (CDDL)
+
+The code released under the CDDL shall be governed by the laws of the
+State of California (excluding conflict-of-law provisions). Any
+litigation relating to this License shall be subject to the jurisdiction
+of the Federal Courts of the Northern District of California and the
+state courts of the State of California, with venue lying in Santa Clara
+County, California.
+
+
+
+
+The GNU General Public License (GPL) Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place,
+Suite 330, Boston, MA 02111-1307 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to
+share and change it. By contrast, the GNU General Public License is
+intended to guarantee your freedom to share and change free software--to
+make sure the software is free for all its users. This General Public
+License applies to most of the Free Software Foundation's software and
+to any other program whose authors commit to using it. (Some other Free
+Software Foundation software is covered by the GNU Library General
+Public License instead.) You can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price.
+Our General Public Licenses are designed to make sure that you have the
+freedom to distribute copies of free software (and charge for this
+service if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone
+to deny you these rights or to ask you to surrender the rights. These
+restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis
+or for a fee, you must give the recipients all the rights that you have.
+You must make sure that they, too, receive or can get the source code.
+And you must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+Finally, any free program is threatened constantly by software patents.
+We wish to avoid the danger that redistributors of a free program will
+individually obtain patent licenses, in effect making the program
+proprietary. To prevent this, we have made it clear that any patent must
+be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a
+notice placed by the copyright holder saying it may be distributed under
+the terms of this General Public License. The "Program", below, refers
+to any such program or work, and a "work based on the Program" means
+either the Program or any derivative work under copyright law: that is
+to say, a work containing the Program or a portion of it, either
+verbatim or with modifications and/or translated into another language.
+(Hereinafter, translation is included without limitation in the term
+"modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of running
+the Program is not restricted, and the output from the Program is
+covered only if its contents constitute a work based on the Program
+(independent of having been made by running the Program). Whether that
+is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source
+code as you receive it, in any medium, provided that you conspicuously
+and appropriately publish on each copy an appropriate copyright notice
+and disclaimer of warranty; keep intact all the notices that refer to
+this License and to the absence of any warranty; and give any other
+recipients of the Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of
+it, thus forming a work based on the Program, and copy and distribute
+such modifications or work under the terms of Section 1 above, provided
+that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices stating
+    that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in whole
+    or in part contains or is derived from the Program or any part thereof,
+    to be licensed as a whole at no charge to all third parties under the
+    terms of this License.
+
+    c) If the modified program normally reads commands interactively when
+    run, you must cause it, when started running for such interactive use in
+    the most ordinary way, to print or display an announcement including an
+    appropriate copyright notice and a notice that there is no warranty (or
+    else, saying that you provide a warranty) and that users may
+    redistribute the program under these conditions, and telling the user
+    how to view a copy of this License. (Exception: if the Program itself is
+    interactive but does not normally print such an announcement, your work
+    based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program, and
+can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based on
+the Program, the distribution of the whole must be on the terms of this
+License, whose permissions for other licensees extend to the entire
+whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of a
+storage or distribution medium does not bring the other work under the
+scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections
+1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable source
+    code, which must be distributed under the terms of Sections 1 and 2
+    above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three years, to
+    give any third party, for a charge no more than your cost of physically
+    performing source distribution, a complete machine-readable copy of the
+    corresponding source code, to be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer to
+    distribute corresponding source code. (This alternative is allowed only
+    for noncommercial distribution and only if you received the program in
+    object code or executable form with such an offer, in accord with
+    Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source code
+means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to control
+compilation and installation of the executable. However, as a special
+exception, the source code distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access
+to copy from a designated place, then offering equivalent access to copy
+the source code from the same place counts as distribution of the source
+code, even though third parties are not compelled to copy the source
+along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt otherwise
+to copy, modify, sublicense or distribute the Program is void, and will
+automatically terminate your rights under this License. However, parties
+who have received copies, or rights, from you under this License will
+not have their licenses terminated so long as such parties remain in
+full compliance.
+
+5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and all
+its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further restrictions
+on the recipients' exercise of the rights granted herein. You are not
+responsible for enforcing compliance by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot distribute
+so as to satisfy simultaneously your obligations under this License and
+any other pertinent obligations, then as a consequence you may not
+distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who
+receive copies directly or indirectly through you, then the only way you
+could satisfy both it and this License would be to refrain entirely from
+distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is implemented
+by public license practices. Many people have made generous
+contributions to the wide range of software distributed through that
+system in reliance on consistent application of that system; it is up to
+the author/donor to decide if he or she is willing to distribute
+software through any other system and a licensee cannot impose that
+choice.
+
+This section is intended to make thoroughly clear what is believed to be
+a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License may
+add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among countries
+not thus excluded. In such case, this License incorporates the
+limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Program does not specify a version
+number of this License, you may choose any version ever published by the
+Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the
+author to ask for permission. For software which is copyrighted by the
+Free Software Foundation, write to the Free Software Foundation; we
+sometimes make exceptions for this. Our decision will be guided by the
+two goals of preserving the free status of all derivatives of our free
+software and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH
+YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
+DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
+DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
+INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
+THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR
+OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these
+terms.
+
+To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively convey
+the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    One line to give the program's name and a brief idea of what it does.
+    Copyright (C) <year> <name of author>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by the
+    Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+    Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author Gnomovision
+    comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is
+    free software, and you are welcome to redistribute it under certain
+    conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the
+appropriate parts of the General Public License. Of course, the commands
+you use may be called something other than `show w' and `show c'; they
+could even be mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+    Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+    `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+    signature of Ty Coon, 1 April 1989
+    Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications
+with the library. If this is what you want to do, use the GNU Library
+General Public License instead of this License.
+
+#
+
+"CLASSPATH" EXCEPTION TO THE GPL VERSION 2
+
+Certain source files distributed by Oracle are subject to the following
+clarification and special exception to the GPL Version 2, but only where
+Oracle has expressly included in the particular source file's header the
+words "Oracle designates this particular file as subject to the
+"Classpath" exception as provided by Oracle in the License file that
+accompanied this code."
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License Version 2 cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under terms
+of your choice, provided that you also meet, for each linked independent
+module, the terms and conditions of the license of that module. An
+independent module is a module which is not derived from or based on
+this library. If you modify this library, you may extend this exception
+to your version of the library, but you are not obligated to do so. If
+you do not wish to do so, delete this exception statement from your
+version.
diff --git a/lib/jersey-core-1.11-sources.jar b/lib/jersey-core-1.11-sources.jar
new file mode 100644
index 0000000..b685f2c
--- /dev/null
+++ b/lib/jersey-core-1.11-sources.jar
Binary files differ
diff --git a/lib/jersey-core-1.11.jar b/lib/jersey-core-1.11.jar
new file mode 100644
index 0000000..d19f7ae
--- /dev/null
+++ b/lib/jersey-core-1.11.jar
Binary files differ
diff --git a/lib/jersey-core-1.11.jar.txt b/lib/jersey-core-1.11.jar.txt
new file mode 100644
index 0000000..8681d13
--- /dev/null
+++ b/lib/jersey-core-1.11.jar.txt
@@ -0,0 +1,712 @@
+COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1
+
+1. Definitions.
+
+    1.1. "Contributor" means each individual or entity that creates or
+    contributes to the creation of Modifications.
+
+    1.2. "Contributor Version" means the combination of the Original
+    Software, prior Modifications used by a Contributor (if any), and the
+    Modifications made by that particular Contributor.
+
+    1.3. "Covered Software" means (a) the Original Software, or (b)
+    Modifications, or (c) the combination of files containing Original
+    Software with files containing Modifications, in each case including
+    portions thereof.
+
+    1.4. "Executable" means the Covered Software in any form other than
+    Source Code.
+
+    1.5. "Initial Developer" means the individual or entity that first makes
+    Original Software available under this License.
+
+    1.6. "Larger Work" means a work which combines Covered Software or
+    portions thereof with code not governed by the terms of this License.
+
+    1.7. "License" means this document.
+
+    1.8. "Licensable" means having the right to grant, to the maximum extent
+    possible, whether at the time of the initial grant or subsequently
+    acquired, any and all of the rights conveyed herein.
+
+    1.9. "Modifications" means the Source Code and Executable form of any of
+    the following:
+
+    A. Any file that results from an addition to, deletion from or
+    modification of the contents of a file containing Original Software or
+    previous Modifications;
+
+    B. Any new file that contains any part of the Original Software or
+    previous Modification; or
+
+    C. Any new file that is contributed or otherwise made available under
+    the terms of this License.
+
+    1.10. "Original Software" means the Source Code and Executable form of
+    computer software code that is originally released under this License.
+
+    1.11. "Patent Claims" means any patent claim(s), now owned or hereafter
+    acquired, including without limitation, method, process, and apparatus
+    claims, in any patent Licensable by grantor.
+
+    1.12. "Source Code" means (a) the common form of computer software code
+    in which modifications are made and (b) associated documentation
+    included in or with such code.
+
+    1.13. "You" (or "Your") means an individual or a legal entity exercising
+    rights under, and complying with all of the terms of, this License. For
+    legal entities, "You" includes any entity which controls, is controlled
+    by, or is under common control with You. For purposes of this
+    definition, "control" means (a) the power, direct or indirect, to cause
+    the direction or management of such entity, whether by contract or
+    otherwise, or (b) ownership of more than fifty percent (50%) of the
+    outstanding shares or beneficial ownership of such entity.
+
+2. License Grants.
+
+    2.1. The Initial Developer Grant.
+
+    Conditioned upon Your compliance with Section 3.1 below and subject to
+    third party intellectual property claims, the Initial Developer hereby
+    grants You a world-wide, royalty-free, non-exclusive license:
+
+    (a) under intellectual property rights (other than patent or trademark)
+    Licensable by Initial Developer, to use, reproduce, modify, display,
+    perform, sublicense and distribute the Original Software (or portions
+    thereof), with or without Modifications, and/or as part of a Larger
+    Work; and
+
+    (b) under Patent Claims infringed by the making, using or selling of
+    Original Software, to make, have made, use, practice, sell, and offer
+    for sale, and/or otherwise dispose of the Original Software (or portions
+    thereof).
+
+    (c) The licenses granted in Sections 2.1(a) and (b) are effective on the
+    date Initial Developer first distributes or otherwise makes the Original
+    Software available to a third party under the terms of this License.
+
+    (d) Notwithstanding Section 2.1(b) above, no patent license is granted:
+    (1) for code that You delete from the Original Software, or (2) for
+    infringements caused by: (i) the modification of the Original Software,
+    or (ii) the combination of the Original Software with other software or
+    devices.
+
+    2.2. Contributor Grant.
+
+    Conditioned upon Your compliance with Section 3.1 below and subject to
+    third party intellectual property claims, each Contributor hereby grants
+    You a world-wide, royalty-free, non-exclusive license:
+
+    (a) under intellectual property rights (other than patent or trademark)
+    Licensable by Contributor to use, reproduce, modify, display, perform,
+    sublicense and distribute the Modifications created by such Contributor
+    (or portions thereof), either on an unmodified basis, with other
+    Modifications, as Covered Software and/or as part of a Larger Work; and
+
+    (b) under Patent Claims infringed by the making, using, or selling of
+    Modifications made by that Contributor either alone and/or in
+    combination with its Contributor Version (or portions of such
+    combination), to make, use, sell, offer for sale, have made, and/or
+    otherwise dispose of: (1) Modifications made by that Contributor (or
+    portions thereof); and (2) the combination of Modifications made by that
+    Contributor with its Contributor Version (or portions of such
+    combination).
+
+    (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on
+    the date Contributor first distributes or otherwise makes the
+    Modifications available to a third party.
+
+    (d) Notwithstanding Section 2.2(b) above, no patent license is granted:
+    (1) for any code that Contributor has deleted from the Contributor
+    Version; (2) for infringements caused by: (i) third party modifications
+    of Contributor Version, or (ii) the combination of Modifications made by
+    that Contributor with other software (except as part of the Contributor
+    Version) or other devices; or (3) under Patent Claims infringed by
+    Covered Software in the absence of Modifications made by that
+    Contributor.
+
+3. Distribution Obligations.
+
+    3.1. Availability of Source Code.
+
+    Any Covered Software that You distribute or otherwise make available in
+    Executable form must also be made available in Source Code form and that
+    Source Code form must be distributed only under the terms of this
+    License. You must include a copy of this License with every copy of the
+    Source Code form of the Covered Software You distribute or otherwise
+    make available. You must inform recipients of any such Covered Software
+    in Executable form as to how they can obtain such Covered Software in
+    Source Code form in a reasonable manner on or through a medium
+    customarily used for software exchange.
+
+    3.2. Modifications.
+
+    The Modifications that You create or to which You contribute are
+    governed by the terms of this License. You represent that You believe
+    Your Modifications are Your original creation(s) and/or You have
+    sufficient rights to grant the rights conveyed by this License.
+
+    3.3. Required Notices.
+
+    You must include a notice in each of Your Modifications that identifies
+    You as the Contributor of the Modification. You may not remove or alter
+    any copyright, patent or trademark notices contained within the Covered
+    Software, or any notices of licensing or any descriptive text giving
+    attribution to any Contributor or the Initial Developer.
+
+    3.4. Application of Additional Terms.
+
+    You may not offer or impose any terms on any Covered Software in Source
+    Code form that alters or restricts the applicable version of this
+    License or the recipients' rights hereunder. You may choose to offer,
+    and to charge a fee for, warranty, support, indemnity or liability
+    obligations to one or more recipients of Covered Software. However, you
+    may do so only on Your own behalf, and not on behalf of the Initial
+    Developer or any Contributor. You must make it absolutely clear that any
+    such warranty, support, indemnity or liability obligation is offered by
+    You alone, and You hereby agree to indemnify the Initial Developer and
+    every Contributor for any liability incurred by the Initial Developer or
+    such Contributor as a result of warranty, support, indemnity or
+    liability terms You offer.
+
+    3.5. Distribution of Executable Versions.
+
+    You may distribute the Executable form of the Covered Software under the
+    terms of this License or under the terms of a license of Your choice,
+    which may contain terms different from this License, provided that You
+    are in compliance with the terms of this License and that the license
+    for the Executable form does not attempt to limit or alter the
+    recipient's rights in the Source Code form from the rights set forth in
+    this License. If You distribute the Covered Software in Executable form
+    under a different license, You must make it absolutely clear that any
+    terms which differ from this License are offered by You alone, not by
+    the Initial Developer or Contributor. You hereby agree to indemnify the
+    Initial Developer and every Contributor for any liability incurred by
+    the Initial Developer or such Contributor as a result of any such terms
+    You offer.
+
+    3.6. Larger Works.
+
+    You may create a Larger Work by combining Covered Software with other
+    code not governed by the terms of this License and distribute the Larger
+    Work as a single product. In such a case, You must make sure the
+    requirements of this License are fulfilled for the Covered Software.
+
+4. Versions of the License.
+
+    4.1. New Versions.
+
+    Oracle is the initial license steward and may publish revised and/or new
+    versions of this License from time to time. Each version will be given a
+    distinguishing version number. Except as provided in Section 4.3, no one
+    other than the license steward has the right to modify this License.
+
+    4.2. Effect of New Versions.
+
+    You may always continue to use, distribute or otherwise make the Covered
+    Software available under the terms of the version of the License under
+    which You originally received the Covered Software. If the Initial
+    Developer includes a notice in the Original Software prohibiting it from
+    being distributed or otherwise made available under any subsequent
+    version of the License, You must distribute and make the Covered
+    Software available under the terms of the version of the License under
+    which You originally received the Covered Software. Otherwise, You may
+    also choose to use, distribute or otherwise make the Covered Software
+    available under the terms of any subsequent version of the License
+    published by the license steward.
+
+    4.3. Modified Versions.
+
+    When You are an Initial Developer and You want to create a new license
+    for Your Original Software, You may create and use a modified version of
+    this License if You: (a) rename the license and remove any references to
+    the name of the license steward (except to note that the license differs
+    from this License); and (b) otherwise make it clear that the license
+    contains terms which differ from this License.
+
+5. DISCLAIMER OF WARRANTY.
+
+    COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+    WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+    WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF
+    DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+    THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
+    SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY
+    RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME
+    THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS
+    DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO
+    USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
+    DISCLAIMER.
+
+6. TERMINATION.
+
+    6.1. This License and the rights granted hereunder will terminate
+    automatically if You fail to comply with terms herein and fail to cure
+    such breach within 30 days of becoming aware of the breach. Provisions
+    which, by their nature, must remain in effect beyond the termination of
+    this License shall survive.
+
+    6.2. If You assert a patent infringement claim (excluding declaratory
+    judgment actions) against Initial Developer or a Contributor (the
+    Initial Developer or Contributor against whom You assert such claim is
+    referred to as "Participant") alleging that the Participant Software
+    (meaning the Contributor Version where the Participant is a Contributor
+    or the Original Software where the Participant is the Initial Developer)
+    directly or indirectly infringes any patent, then any and all rights
+    granted directly or indirectly to You by such Participant, the Initial
+    Developer (if the Initial Developer is not the Participant) and all
+    Contributors under Sections 2.1 and/or 2.2 of this License shall, upon
+    60 days notice from Participant terminate prospectively and
+    automatically at the expiration of such 60 day notice period, unless if
+    within such 60 day period You withdraw Your claim with respect to the
+    Participant Software against such Participant either unilaterally or
+    pursuant to a written agreement with Participant.
+
+    6.3. If You assert a patent infringement claim against Participant
+    alleging that the Participant Software directly or indirectly infringes
+    any patent where such claim is resolved (such as by license or
+    settlement) prior to the initiation of patent infringement litigation,
+    then the reasonable value of the licenses granted by such Participant
+    under Sections 2.1 or 2.2 shall be taken into account in determining the
+    amount or value of any payment or license.
+
+    6.4. In the event of termination under Sections 6.1 or 6.2 above, all
+    end user licenses that have been validly granted by You or any
+    distributor hereunder prior to termination (excluding licenses granted
+    to You by any distributor) shall survive termination.
+
+7. LIMITATION OF LIABILITY.
+
+    UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+    (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+    DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED
+    SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY
+    PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+    OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF
+    GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL
+    OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+    INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+    LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+    RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+    PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
+    OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION
+    AND LIMITATION MAY NOT APPLY TO YOU.
+
+8. U.S. GOVERNMENT END USERS.
+
+    The Covered Software is a "commercial item," as that term is defined in
+    48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+    software" (as that term is defined at 48 C.F.R. § 252.227-7014(a)(1))
+    and "commercial computer software documentation" as such terms are used
+    in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and
+    48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government
+    End Users acquire Covered Software with only those rights set forth
+    herein. This U.S. Government Rights clause is in lieu of, and
+    supersedes, any other FAR, DFAR, or other clause or provision that
+    addresses Government rights in computer software under this License.
+
+9. MISCELLANEOUS.
+
+    This License represents the complete agreement concerning subject matter
+    hereof. If any provision of this License is held to be unenforceable,
+    such provision shall be reformed only to the extent necessary to make it
+    enforceable. This License shall be governed by the law of the
+    jurisdiction specified in a notice contained within the Original
+    Software (except to the extent applicable law, if any, provides
+    otherwise), excluding such jurisdiction's conflict-of-law provisions.
+    Any litigation relating to this License shall be subject to the
+    jurisdiction of the courts located in the jurisdiction and venue
+    specified in a notice contained within the Original Software, with the
+    losing party responsible for costs, including, without limitation, court
+    costs and reasonable attorneys' fees and expenses. The application of
+    the United Nations Convention on Contracts for the International Sale of
+    Goods is expressly excluded. Any law or regulation which provides that
+    the language of a contract shall be construed against the drafter shall
+    not apply to this License. You agree that You alone are responsible for
+    compliance with the United States export administration regulations (and
+    the export control laws and regulation of any other countries) when You
+    use, distribute or otherwise make available any Covered Software.
+
+10. RESPONSIBILITY FOR CLAIMS.
+
+    As between Initial Developer and the Contributors, each party is
+    responsible for claims and damages arising, directly or indirectly, out
+    of its utilization of rights under this License and You agree to work
+    with Initial Developer and Contributors to distribute such
+    responsibility on an equitable basis. Nothing herein is intended or
+    shall be deemed to constitute any admission of liability.
+
+NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION
+LICENSE (CDDL)
+
+The code released under the CDDL shall be governed by the laws of the
+State of California (excluding conflict-of-law provisions). Any
+litigation relating to this License shall be subject to the jurisdiction
+of the Federal Courts of the Northern District of California and the
+state courts of the State of California, with venue lying in Santa Clara
+County, California.
+
+
+
+
+The GNU General Public License (GPL) Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place,
+Suite 330, Boston, MA 02111-1307 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to
+share and change it. By contrast, the GNU General Public License is
+intended to guarantee your freedom to share and change free software--to
+make sure the software is free for all its users. This General Public
+License applies to most of the Free Software Foundation's software and
+to any other program whose authors commit to using it. (Some other Free
+Software Foundation software is covered by the GNU Library General
+Public License instead.) You can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price.
+Our General Public Licenses are designed to make sure that you have the
+freedom to distribute copies of free software (and charge for this
+service if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone
+to deny you these rights or to ask you to surrender the rights. These
+restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis
+or for a fee, you must give the recipients all the rights that you have.
+You must make sure that they, too, receive or can get the source code.
+And you must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+Finally, any free program is threatened constantly by software patents.
+We wish to avoid the danger that redistributors of a free program will
+individually obtain patent licenses, in effect making the program
+proprietary. To prevent this, we have made it clear that any patent must
+be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a
+notice placed by the copyright holder saying it may be distributed under
+the terms of this General Public License. The "Program", below, refers
+to any such program or work, and a "work based on the Program" means
+either the Program or any derivative work under copyright law: that is
+to say, a work containing the Program or a portion of it, either
+verbatim or with modifications and/or translated into another language.
+(Hereinafter, translation is included without limitation in the term
+"modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of running
+the Program is not restricted, and the output from the Program is
+covered only if its contents constitute a work based on the Program
+(independent of having been made by running the Program). Whether that
+is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source
+code as you receive it, in any medium, provided that you conspicuously
+and appropriately publish on each copy an appropriate copyright notice
+and disclaimer of warranty; keep intact all the notices that refer to
+this License and to the absence of any warranty; and give any other
+recipients of the Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of
+it, thus forming a work based on the Program, and copy and distribute
+such modifications or work under the terms of Section 1 above, provided
+that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices stating
+    that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in whole
+    or in part contains or is derived from the Program or any part thereof,
+    to be licensed as a whole at no charge to all third parties under the
+    terms of this License.
+
+    c) If the modified program normally reads commands interactively when
+    run, you must cause it, when started running for such interactive use in
+    the most ordinary way, to print or display an announcement including an
+    appropriate copyright notice and a notice that there is no warranty (or
+    else, saying that you provide a warranty) and that users may
+    redistribute the program under these conditions, and telling the user
+    how to view a copy of this License. (Exception: if the Program itself is
+    interactive but does not normally print such an announcement, your work
+    based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program, and
+can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based on
+the Program, the distribution of the whole must be on the terms of this
+License, whose permissions for other licensees extend to the entire
+whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of a
+storage or distribution medium does not bring the other work under the
+scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections
+1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable source
+    code, which must be distributed under the terms of Sections 1 and 2
+    above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three years, to
+    give any third party, for a charge no more than your cost of physically
+    performing source distribution, a complete machine-readable copy of the
+    corresponding source code, to be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer to
+    distribute corresponding source code. (This alternative is allowed only
+    for noncommercial distribution and only if you received the program in
+    object code or executable form with such an offer, in accord with
+    Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source code
+means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to control
+compilation and installation of the executable. However, as a special
+exception, the source code distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access
+to copy from a designated place, then offering equivalent access to copy
+the source code from the same place counts as distribution of the source
+code, even though third parties are not compelled to copy the source
+along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt otherwise
+to copy, modify, sublicense or distribute the Program is void, and will
+automatically terminate your rights under this License. However, parties
+who have received copies, or rights, from you under this License will
+not have their licenses terminated so long as such parties remain in
+full compliance.
+
+5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and all
+its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further restrictions
+on the recipients' exercise of the rights granted herein. You are not
+responsible for enforcing compliance by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot distribute
+so as to satisfy simultaneously your obligations under this License and
+any other pertinent obligations, then as a consequence you may not
+distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who
+receive copies directly or indirectly through you, then the only way you
+could satisfy both it and this License would be to refrain entirely from
+distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is implemented
+by public license practices. Many people have made generous
+contributions to the wide range of software distributed through that
+system in reliance on consistent application of that system; it is up to
+the author/donor to decide if he or she is willing to distribute
+software through any other system and a licensee cannot impose that
+choice.
+
+This section is intended to make thoroughly clear what is believed to be
+a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License may
+add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among countries
+not thus excluded. In such case, this License incorporates the
+limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Program does not specify a version
+number of this License, you may choose any version ever published by the
+Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the
+author to ask for permission. For software which is copyrighted by the
+Free Software Foundation, write to the Free Software Foundation; we
+sometimes make exceptions for this. Our decision will be guided by the
+two goals of preserving the free status of all derivatives of our free
+software and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH
+YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
+DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
+DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
+INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
+THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR
+OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these
+terms.
+
+To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively convey
+the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    One line to give the program's name and a brief idea of what it does.
+    Copyright (C) <year> <name of author>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by the
+    Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+    Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author Gnomovision
+    comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is
+    free software, and you are welcome to redistribute it under certain
+    conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the
+appropriate parts of the General Public License. Of course, the commands
+you use may be called something other than `show w' and `show c'; they
+could even be mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+    Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+    `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+    signature of Ty Coon, 1 April 1989
+    Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications
+with the library. If this is what you want to do, use the GNU Library
+General Public License instead of this License.
+
+#
+
+"CLASSPATH" EXCEPTION TO THE GPL VERSION 2
+
+Certain source files distributed by Oracle are subject to the following
+clarification and special exception to the GPL Version 2, but only where
+Oracle has expressly included in the particular source file's header the
+words "Oracle designates this particular file as subject to the
+"Classpath" exception as provided by Oracle in the License file that
+accompanied this code."
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License Version 2 cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under terms
+of your choice, provided that you also meet, for each linked independent
+module, the terms and conditions of the license of that module. An
+independent module is a module which is not derived from or based on
+this library. If you modify this library, you may extend this exception
+to your version of the library, but you are not obligated to do so. If
+you do not wish to do so, delete this exception statement from your
+version.
diff --git a/lib/joda-time-2.1-sources.jar b/lib/joda-time-2.1-sources.jar
new file mode 100644
index 0000000..44e4ed8
--- /dev/null
+++ b/lib/joda-time-2.1-sources.jar
Binary files differ
diff --git a/lib/joda-time-2.1.jar b/lib/joda-time-2.1.jar
new file mode 100644
index 0000000..b2aca95
--- /dev/null
+++ b/lib/joda-time-2.1.jar
Binary files differ
diff --git a/lib/joda-time-2.1.jar.txt b/lib/joda-time-2.1.jar.txt
new file mode 100644
index 0000000..230f1d4
--- /dev/null
+++ b/lib/joda-time-2.1.jar.txt
@@ -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 2008-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.

diff --git a/lib/jsr311-api-1.1.1-sources.jar b/lib/jsr311-api-1.1.1-sources.jar
new file mode 100644
index 0000000..2cd5eb8
--- /dev/null
+++ b/lib/jsr311-api-1.1.1-sources.jar
Binary files differ
diff --git a/lib/jsr311-api-1.1.1.jar b/lib/jsr311-api-1.1.1.jar
new file mode 100644
index 0000000..ec8bc81
--- /dev/null
+++ b/lib/jsr311-api-1.1.1.jar
Binary files differ
diff --git a/lib/jsr311-api-1.1.1.jar.txt b/lib/jsr311-api-1.1.1.jar.txt
new file mode 100644
index 0000000..8681d13
--- /dev/null
+++ b/lib/jsr311-api-1.1.1.jar.txt
@@ -0,0 +1,712 @@
+COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1
+
+1. Definitions.
+
+    1.1. "Contributor" means each individual or entity that creates or
+    contributes to the creation of Modifications.
+
+    1.2. "Contributor Version" means the combination of the Original
+    Software, prior Modifications used by a Contributor (if any), and the
+    Modifications made by that particular Contributor.
+
+    1.3. "Covered Software" means (a) the Original Software, or (b)
+    Modifications, or (c) the combination of files containing Original
+    Software with files containing Modifications, in each case including
+    portions thereof.
+
+    1.4. "Executable" means the Covered Software in any form other than
+    Source Code.
+
+    1.5. "Initial Developer" means the individual or entity that first makes
+    Original Software available under this License.
+
+    1.6. "Larger Work" means a work which combines Covered Software or
+    portions thereof with code not governed by the terms of this License.
+
+    1.7. "License" means this document.
+
+    1.8. "Licensable" means having the right to grant, to the maximum extent
+    possible, whether at the time of the initial grant or subsequently
+    acquired, any and all of the rights conveyed herein.
+
+    1.9. "Modifications" means the Source Code and Executable form of any of
+    the following:
+
+    A. Any file that results from an addition to, deletion from or
+    modification of the contents of a file containing Original Software or
+    previous Modifications;
+
+    B. Any new file that contains any part of the Original Software or
+    previous Modification; or
+
+    C. Any new file that is contributed or otherwise made available under
+    the terms of this License.
+
+    1.10. "Original Software" means the Source Code and Executable form of
+    computer software code that is originally released under this License.
+
+    1.11. "Patent Claims" means any patent claim(s), now owned or hereafter
+    acquired, including without limitation, method, process, and apparatus
+    claims, in any patent Licensable by grantor.
+
+    1.12. "Source Code" means (a) the common form of computer software code
+    in which modifications are made and (b) associated documentation
+    included in or with such code.
+
+    1.13. "You" (or "Your") means an individual or a legal entity exercising
+    rights under, and complying with all of the terms of, this License. For
+    legal entities, "You" includes any entity which controls, is controlled
+    by, or is under common control with You. For purposes of this
+    definition, "control" means (a) the power, direct or indirect, to cause
+    the direction or management of such entity, whether by contract or
+    otherwise, or (b) ownership of more than fifty percent (50%) of the
+    outstanding shares or beneficial ownership of such entity.
+
+2. License Grants.
+
+    2.1. The Initial Developer Grant.
+
+    Conditioned upon Your compliance with Section 3.1 below and subject to
+    third party intellectual property claims, the Initial Developer hereby
+    grants You a world-wide, royalty-free, non-exclusive license:
+
+    (a) under intellectual property rights (other than patent or trademark)
+    Licensable by Initial Developer, to use, reproduce, modify, display,
+    perform, sublicense and distribute the Original Software (or portions
+    thereof), with or without Modifications, and/or as part of a Larger
+    Work; and
+
+    (b) under Patent Claims infringed by the making, using or selling of
+    Original Software, to make, have made, use, practice, sell, and offer
+    for sale, and/or otherwise dispose of the Original Software (or portions
+    thereof).
+
+    (c) The licenses granted in Sections 2.1(a) and (b) are effective on the
+    date Initial Developer first distributes or otherwise makes the Original
+    Software available to a third party under the terms of this License.
+
+    (d) Notwithstanding Section 2.1(b) above, no patent license is granted:
+    (1) for code that You delete from the Original Software, or (2) for
+    infringements caused by: (i) the modification of the Original Software,
+    or (ii) the combination of the Original Software with other software or
+    devices.
+
+    2.2. Contributor Grant.
+
+    Conditioned upon Your compliance with Section 3.1 below and subject to
+    third party intellectual property claims, each Contributor hereby grants
+    You a world-wide, royalty-free, non-exclusive license:
+
+    (a) under intellectual property rights (other than patent or trademark)
+    Licensable by Contributor to use, reproduce, modify, display, perform,
+    sublicense and distribute the Modifications created by such Contributor
+    (or portions thereof), either on an unmodified basis, with other
+    Modifications, as Covered Software and/or as part of a Larger Work; and
+
+    (b) under Patent Claims infringed by the making, using, or selling of
+    Modifications made by that Contributor either alone and/or in
+    combination with its Contributor Version (or portions of such
+    combination), to make, use, sell, offer for sale, have made, and/or
+    otherwise dispose of: (1) Modifications made by that Contributor (or
+    portions thereof); and (2) the combination of Modifications made by that
+    Contributor with its Contributor Version (or portions of such
+    combination).
+
+    (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on
+    the date Contributor first distributes or otherwise makes the
+    Modifications available to a third party.
+
+    (d) Notwithstanding Section 2.2(b) above, no patent license is granted:
+    (1) for any code that Contributor has deleted from the Contributor
+    Version; (2) for infringements caused by: (i) third party modifications
+    of Contributor Version, or (ii) the combination of Modifications made by
+    that Contributor with other software (except as part of the Contributor
+    Version) or other devices; or (3) under Patent Claims infringed by
+    Covered Software in the absence of Modifications made by that
+    Contributor.
+
+3. Distribution Obligations.
+
+    3.1. Availability of Source Code.
+
+    Any Covered Software that You distribute or otherwise make available in
+    Executable form must also be made available in Source Code form and that
+    Source Code form must be distributed only under the terms of this
+    License. You must include a copy of this License with every copy of the
+    Source Code form of the Covered Software You distribute or otherwise
+    make available. You must inform recipients of any such Covered Software
+    in Executable form as to how they can obtain such Covered Software in
+    Source Code form in a reasonable manner on or through a medium
+    customarily used for software exchange.
+
+    3.2. Modifications.
+
+    The Modifications that You create or to which You contribute are
+    governed by the terms of this License. You represent that You believe
+    Your Modifications are Your original creation(s) and/or You have
+    sufficient rights to grant the rights conveyed by this License.
+
+    3.3. Required Notices.
+
+    You must include a notice in each of Your Modifications that identifies
+    You as the Contributor of the Modification. You may not remove or alter
+    any copyright, patent or trademark notices contained within the Covered
+    Software, or any notices of licensing or any descriptive text giving
+    attribution to any Contributor or the Initial Developer.
+
+    3.4. Application of Additional Terms.
+
+    You may not offer or impose any terms on any Covered Software in Source
+    Code form that alters or restricts the applicable version of this
+    License or the recipients' rights hereunder. You may choose to offer,
+    and to charge a fee for, warranty, support, indemnity or liability
+    obligations to one or more recipients of Covered Software. However, you
+    may do so only on Your own behalf, and not on behalf of the Initial
+    Developer or any Contributor. You must make it absolutely clear that any
+    such warranty, support, indemnity or liability obligation is offered by
+    You alone, and You hereby agree to indemnify the Initial Developer and
+    every Contributor for any liability incurred by the Initial Developer or
+    such Contributor as a result of warranty, support, indemnity or
+    liability terms You offer.
+
+    3.5. Distribution of Executable Versions.
+
+    You may distribute the Executable form of the Covered Software under the
+    terms of this License or under the terms of a license of Your choice,
+    which may contain terms different from this License, provided that You
+    are in compliance with the terms of this License and that the license
+    for the Executable form does not attempt to limit or alter the
+    recipient's rights in the Source Code form from the rights set forth in
+    this License. If You distribute the Covered Software in Executable form
+    under a different license, You must make it absolutely clear that any
+    terms which differ from this License are offered by You alone, not by
+    the Initial Developer or Contributor. You hereby agree to indemnify the
+    Initial Developer and every Contributor for any liability incurred by
+    the Initial Developer or such Contributor as a result of any such terms
+    You offer.
+
+    3.6. Larger Works.
+
+    You may create a Larger Work by combining Covered Software with other
+    code not governed by the terms of this License and distribute the Larger
+    Work as a single product. In such a case, You must make sure the
+    requirements of this License are fulfilled for the Covered Software.
+
+4. Versions of the License.
+
+    4.1. New Versions.
+
+    Oracle is the initial license steward and may publish revised and/or new
+    versions of this License from time to time. Each version will be given a
+    distinguishing version number. Except as provided in Section 4.3, no one
+    other than the license steward has the right to modify this License.
+
+    4.2. Effect of New Versions.
+
+    You may always continue to use, distribute or otherwise make the Covered
+    Software available under the terms of the version of the License under
+    which You originally received the Covered Software. If the Initial
+    Developer includes a notice in the Original Software prohibiting it from
+    being distributed or otherwise made available under any subsequent
+    version of the License, You must distribute and make the Covered
+    Software available under the terms of the version of the License under
+    which You originally received the Covered Software. Otherwise, You may
+    also choose to use, distribute or otherwise make the Covered Software
+    available under the terms of any subsequent version of the License
+    published by the license steward.
+
+    4.3. Modified Versions.
+
+    When You are an Initial Developer and You want to create a new license
+    for Your Original Software, You may create and use a modified version of
+    this License if You: (a) rename the license and remove any references to
+    the name of the license steward (except to note that the license differs
+    from this License); and (b) otherwise make it clear that the license
+    contains terms which differ from this License.
+
+5. DISCLAIMER OF WARRANTY.
+
+    COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+    WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+    WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF
+    DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+    THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
+    SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY
+    RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME
+    THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS
+    DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO
+    USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
+    DISCLAIMER.
+
+6. TERMINATION.
+
+    6.1. This License and the rights granted hereunder will terminate
+    automatically if You fail to comply with terms herein and fail to cure
+    such breach within 30 days of becoming aware of the breach. Provisions
+    which, by their nature, must remain in effect beyond the termination of
+    this License shall survive.
+
+    6.2. If You assert a patent infringement claim (excluding declaratory
+    judgment actions) against Initial Developer or a Contributor (the
+    Initial Developer or Contributor against whom You assert such claim is
+    referred to as "Participant") alleging that the Participant Software
+    (meaning the Contributor Version where the Participant is a Contributor
+    or the Original Software where the Participant is the Initial Developer)
+    directly or indirectly infringes any patent, then any and all rights
+    granted directly or indirectly to You by such Participant, the Initial
+    Developer (if the Initial Developer is not the Participant) and all
+    Contributors under Sections 2.1 and/or 2.2 of this License shall, upon
+    60 days notice from Participant terminate prospectively and
+    automatically at the expiration of such 60 day notice period, unless if
+    within such 60 day period You withdraw Your claim with respect to the
+    Participant Software against such Participant either unilaterally or
+    pursuant to a written agreement with Participant.
+
+    6.3. If You assert a patent infringement claim against Participant
+    alleging that the Participant Software directly or indirectly infringes
+    any patent where such claim is resolved (such as by license or
+    settlement) prior to the initiation of patent infringement litigation,
+    then the reasonable value of the licenses granted by such Participant
+    under Sections 2.1 or 2.2 shall be taken into account in determining the
+    amount or value of any payment or license.
+
+    6.4. In the event of termination under Sections 6.1 or 6.2 above, all
+    end user licenses that have been validly granted by You or any
+    distributor hereunder prior to termination (excluding licenses granted
+    to You by any distributor) shall survive termination.
+
+7. LIMITATION OF LIABILITY.
+
+    UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+    (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+    DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED
+    SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY
+    PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+    OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF
+    GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL
+    OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+    INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+    LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+    RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+    PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
+    OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION
+    AND LIMITATION MAY NOT APPLY TO YOU.
+
+8. U.S. GOVERNMENT END USERS.
+
+    The Covered Software is a "commercial item," as that term is defined in
+    48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+    software" (as that term is defined at 48 C.F.R. § 252.227-7014(a)(1))
+    and "commercial computer software documentation" as such terms are used
+    in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and
+    48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government
+    End Users acquire Covered Software with only those rights set forth
+    herein. This U.S. Government Rights clause is in lieu of, and
+    supersedes, any other FAR, DFAR, or other clause or provision that
+    addresses Government rights in computer software under this License.
+
+9. MISCELLANEOUS.
+
+    This License represents the complete agreement concerning subject matter
+    hereof. If any provision of this License is held to be unenforceable,
+    such provision shall be reformed only to the extent necessary to make it
+    enforceable. This License shall be governed by the law of the
+    jurisdiction specified in a notice contained within the Original
+    Software (except to the extent applicable law, if any, provides
+    otherwise), excluding such jurisdiction's conflict-of-law provisions.
+    Any litigation relating to this License shall be subject to the
+    jurisdiction of the courts located in the jurisdiction and venue
+    specified in a notice contained within the Original Software, with the
+    losing party responsible for costs, including, without limitation, court
+    costs and reasonable attorneys' fees and expenses. The application of
+    the United Nations Convention on Contracts for the International Sale of
+    Goods is expressly excluded. Any law or regulation which provides that
+    the language of a contract shall be construed against the drafter shall
+    not apply to this License. You agree that You alone are responsible for
+    compliance with the United States export administration regulations (and
+    the export control laws and regulation of any other countries) when You
+    use, distribute or otherwise make available any Covered Software.
+
+10. RESPONSIBILITY FOR CLAIMS.
+
+    As between Initial Developer and the Contributors, each party is
+    responsible for claims and damages arising, directly or indirectly, out
+    of its utilization of rights under this License and You agree to work
+    with Initial Developer and Contributors to distribute such
+    responsibility on an equitable basis. Nothing herein is intended or
+    shall be deemed to constitute any admission of liability.
+
+NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION
+LICENSE (CDDL)
+
+The code released under the CDDL shall be governed by the laws of the
+State of California (excluding conflict-of-law provisions). Any
+litigation relating to this License shall be subject to the jurisdiction
+of the Federal Courts of the Northern District of California and the
+state courts of the State of California, with venue lying in Santa Clara
+County, California.
+
+
+
+
+The GNU General Public License (GPL) Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place,
+Suite 330, Boston, MA 02111-1307 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to
+share and change it. By contrast, the GNU General Public License is
+intended to guarantee your freedom to share and change free software--to
+make sure the software is free for all its users. This General Public
+License applies to most of the Free Software Foundation's software and
+to any other program whose authors commit to using it. (Some other Free
+Software Foundation software is covered by the GNU Library General
+Public License instead.) You can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price.
+Our General Public Licenses are designed to make sure that you have the
+freedom to distribute copies of free software (and charge for this
+service if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone
+to deny you these rights or to ask you to surrender the rights. These
+restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis
+or for a fee, you must give the recipients all the rights that you have.
+You must make sure that they, too, receive or can get the source code.
+And you must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+Finally, any free program is threatened constantly by software patents.
+We wish to avoid the danger that redistributors of a free program will
+individually obtain patent licenses, in effect making the program
+proprietary. To prevent this, we have made it clear that any patent must
+be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a
+notice placed by the copyright holder saying it may be distributed under
+the terms of this General Public License. The "Program", below, refers
+to any such program or work, and a "work based on the Program" means
+either the Program or any derivative work under copyright law: that is
+to say, a work containing the Program or a portion of it, either
+verbatim or with modifications and/or translated into another language.
+(Hereinafter, translation is included without limitation in the term
+"modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of running
+the Program is not restricted, and the output from the Program is
+covered only if its contents constitute a work based on the Program
+(independent of having been made by running the Program). Whether that
+is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source
+code as you receive it, in any medium, provided that you conspicuously
+and appropriately publish on each copy an appropriate copyright notice
+and disclaimer of warranty; keep intact all the notices that refer to
+this License and to the absence of any warranty; and give any other
+recipients of the Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of
+it, thus forming a work based on the Program, and copy and distribute
+such modifications or work under the terms of Section 1 above, provided
+that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices stating
+    that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in whole
+    or in part contains or is derived from the Program or any part thereof,
+    to be licensed as a whole at no charge to all third parties under the
+    terms of this License.
+
+    c) If the modified program normally reads commands interactively when
+    run, you must cause it, when started running for such interactive use in
+    the most ordinary way, to print or display an announcement including an
+    appropriate copyright notice and a notice that there is no warranty (or
+    else, saying that you provide a warranty) and that users may
+    redistribute the program under these conditions, and telling the user
+    how to view a copy of this License. (Exception: if the Program itself is
+    interactive but does not normally print such an announcement, your work
+    based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program, and
+can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based on
+the Program, the distribution of the whole must be on the terms of this
+License, whose permissions for other licensees extend to the entire
+whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of a
+storage or distribution medium does not bring the other work under the
+scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections
+1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable source
+    code, which must be distributed under the terms of Sections 1 and 2
+    above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three years, to
+    give any third party, for a charge no more than your cost of physically
+    performing source distribution, a complete machine-readable copy of the
+    corresponding source code, to be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer to
+    distribute corresponding source code. (This alternative is allowed only
+    for noncommercial distribution and only if you received the program in
+    object code or executable form with such an offer, in accord with
+    Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source code
+means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to control
+compilation and installation of the executable. However, as a special
+exception, the source code distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access
+to copy from a designated place, then offering equivalent access to copy
+the source code from the same place counts as distribution of the source
+code, even though third parties are not compelled to copy the source
+along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt otherwise
+to copy, modify, sublicense or distribute the Program is void, and will
+automatically terminate your rights under this License. However, parties
+who have received copies, or rights, from you under this License will
+not have their licenses terminated so long as such parties remain in
+full compliance.
+
+5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and all
+its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further restrictions
+on the recipients' exercise of the rights granted herein. You are not
+responsible for enforcing compliance by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot distribute
+so as to satisfy simultaneously your obligations under this License and
+any other pertinent obligations, then as a consequence you may not
+distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who
+receive copies directly or indirectly through you, then the only way you
+could satisfy both it and this License would be to refrain entirely from
+distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is implemented
+by public license practices. Many people have made generous
+contributions to the wide range of software distributed through that
+system in reliance on consistent application of that system; it is up to
+the author/donor to decide if he or she is willing to distribute
+software through any other system and a licensee cannot impose that
+choice.
+
+This section is intended to make thoroughly clear what is believed to be
+a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License may
+add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among countries
+not thus excluded. In such case, this License incorporates the
+limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Program does not specify a version
+number of this License, you may choose any version ever published by the
+Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the
+author to ask for permission. For software which is copyrighted by the
+Free Software Foundation, write to the Free Software Foundation; we
+sometimes make exceptions for this. Our decision will be guided by the
+two goals of preserving the free status of all derivatives of our free
+software and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH
+YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
+DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
+DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
+INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
+THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR
+OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these
+terms.
+
+To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively convey
+the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    One line to give the program's name and a brief idea of what it does.
+    Copyright (C) <year> <name of author>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by the
+    Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+    Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author Gnomovision
+    comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is
+    free software, and you are welcome to redistribute it under certain
+    conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the
+appropriate parts of the General Public License. Of course, the commands
+you use may be called something other than `show w' and `show c'; they
+could even be mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+    Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+    `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+    signature of Ty Coon, 1 April 1989
+    Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications
+with the library. If this is what you want to do, use the GNU Library
+General Public License instead of this License.
+
+#
+
+"CLASSPATH" EXCEPTION TO THE GPL VERSION 2
+
+Certain source files distributed by Oracle are subject to the following
+clarification and special exception to the GPL Version 2, but only where
+Oracle has expressly included in the particular source file's header the
+words "Oracle designates this particular file as subject to the
+"Classpath" exception as provided by Oracle in the License file that
+accompanied this code."
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License Version 2 cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under terms
+of your choice, provided that you also meet, for each linked independent
+module, the terms and conditions of the license of that module. An
+independent module is a module which is not derived from or based on
+this library. If you modify this library, you may extend this exception
+to your version of the library, but you are not obligated to do so. If
+you do not wish to do so, delete this exception statement from your
+version.
diff --git a/tutorial/Tutorial.java b/tutorial/Tutorial.java
index 625d387..025a4d5 100644
--- a/tutorial/Tutorial.java
+++ b/tutorial/Tutorial.java
@@ -16,8 +16,9 @@
 
 package tutorial;
 
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
 import com.google.caliper.Param;
-import com.google.caliper.SimpleBenchmark;
 
 /**
  * Caliper tutorial. To run the example benchmarks in this file:
@@ -31,7 +32,7 @@
    *
    * Notice:
    *
-   *  - We write a class that extends com.google.caliper.SimpleBenchmark.
+   *  - We write a class that extends com.google.caliper.Benchmark.
    *  - 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,
@@ -48,8 +49,8 @@
    *    ---------  ---
    *    NanoTime   233
    */
-  public static class Benchmark1 extends SimpleBenchmark {
-    public void timeNanoTime(int reps) {
+  public static class Benchmark1 {
+    @Benchmark void timeNanoTime(int reps) {
       for (int i = 0; i < reps; i++) {
         System.nanoTime();
       }
@@ -69,13 +70,13 @@
    *   NanoTime           248
    *   CurrentTimeMillis  118
    */
-  public static class Benchmark2 extends SimpleBenchmark {
-    public void timeNanoTime(int reps) {
+  public static class Benchmark2 {
+    @Benchmark void timeNanoTime(int reps) {
       for (int i = 0; i < reps; i++) {
         System.nanoTime();
       }
     }
-    public void timeCurrentTimeMillis(int reps) {
+    @Benchmark void timeCurrentTimeMillis(int reps) {
       for (int i = 0; i < reps; i++) {
         System.currentTimeMillis();
       }
@@ -86,11 +87,11 @@
    * Let's try iterating over a large array. This seems simple enough, but
    * there is a problem!
    */
-  public static class Benchmark3 extends SimpleBenchmark {
+  public static class Benchmark3 {
     private final int[] array = new int[1000000];
 
     @SuppressWarnings("UnusedDeclaration") // IDEA tries to warn us!
-    public void timeArrayIteration_BAD(int reps) {
+    @Benchmark void timeArrayIteration_BAD(int reps) {
       for (int i = 0; i < reps; i++) {
         for (int ignoreMe : array) {}
       }
@@ -123,10 +124,10 @@
    * 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 {
+  public static class Benchmark4 {
     private final int[] array = new int[1000000];
 
-    public int timeArrayIteration_fixed(int reps) {
+    @Benchmark int timeArrayIteration_fixed(int reps) {
       int dummy = 0;
       for (int i = 0; i < reps; i++) {
         for (int doNotIgnoreMe : array) {
@@ -164,17 +165,17 @@
    *   ArrayIteration  1000  477 ||||||||||||||||||||||||||||||
    *
    */
-  public static class Benchmark5 extends SimpleBenchmark {
+  public static class Benchmark5 {
     @Param int size; // set automatically by framework
 
     private int[] array; // set by us, in setUp()
 
-    @Override protected void setUp() {
+    @BeforeExperiment void setUp() {
       // @Param values are guaranteed to have been injected by now
       array = new int[size];
     }
 
-    public int timeArrayIteration(int reps) {
+    @Benchmark int timeArrayIteration(int reps) {
       int dummy = 0;
       for (int i = 0; i < reps; i++) {
         for (int doNotIgnoreMe : array) {