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

This does not upgrade to a released version of Caliper, it's
just a snapshot, see README.android for details.

Since 0.5-rc1 Caliper has gone through a huge number of changes
but it still does essentially the same job in the same way it
is just more flexible and capable than the previous version.

One major change that impacts the build is that this version
uses the Dagger2 dependency injection framework that generates
some of its code using annotation processors and so it was
necessary to change the build to use Dagger2.

Not all the Caliper tests run on Android, some are written for
the JVM and rely on some characteristic of it that is not the
same on Android, whether it be available memory, exception
message, supported classes or file structure. There are also a
couple of failures that are not completely explainable at the
moment. The details of these failures are listed in the
expectations/knownfailures.txt and will be addressed at a later
date.

Bug: 24848946
Change-Id: Ia245db56c57315ce18db8eb219003fecf3c64ab9
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) {