caliper/src/main/java/com/google/caliper/model/BenchmarkSpec.java
@Entity
@Access(FIELD)
@Immutable
@Cacheable
@NamedQuery(
    name = "listBenchmarkSpecsForHash",
    query = "SELECT b FROM BenchmarkSpec b WHERE hash = :hash",
    hints = {
        @QueryHint(name = "org.hibernate.cacheable", value = "true"),
        @QueryHint(name = "org.hibernate.readOnly", value = "true")})
public final class BenchmarkSpec {
  static final BenchmarkSpec DEFAULT = new BenchmarkSpec();

  @Id @GeneratedValue @ExcludeFromJson private int id;
  @Basic(optional = false) private String className;
  @Basic(optional = false) private String methodName;
  @ElementCollection @Sort(type = NATURAL) private SortedMap<String, String> parameters;
  @ExcludeFromJson private int hash;

  private BenchmarkSpec() {
    this.className = "";
    this.methodName = "";
    this.parameters = Maps.newTreeMap();
    this.hash = 0;
  }

  private BenchmarkSpec(Builder builder) {
    this.className = builder.className;
    this.methodName = builder.methodName;
    this.parameters = Maps.newTreeMap(builder.parameters);
    this.hash = getPersistentHashFunction().hashObject(this, BenchmarkSpecFunnel.INSTANCE).asInt();
  }

  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.hash == that.hash)
          && this.className.equals(that.className)
          && this.methodName.equals(that.methodName)
          && this.parameters.equals(that.parameters);
    } else {
      return false;
    }
  }

  @Override public int hashCode() {
    return hash;
  }

  @Override public String toString() {
    return Objects.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.putString(from.className)
          .putString(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);
    }
  }
}
caliper/src/main/java/com/google/caliper/bridge/FailureLogMessage.java
public class FailureLogMessage extends CaliperControlLogMessage {
  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;
    }
  }
}
caliper/src/main/java/com/google/caliper/runner/Instrument.java
interface MeasurementCollectingVisitor extends LogMessageVisitor {
  boolean isDoneCollecting();
  ImmutableList<Measurement> getMeasurements();
}
caliper/src/main/java/com/google/caliper/model/Host.java
@Entity
@Access(FIELD)
@Immutable
@Cacheable
@NamedQuery(
    name = "listHostsForHash",
    query = "SELECT h FROM Host h WHERE hash = :hash",
    hints = {
        @QueryHint(name = "org.hibernate.cacheable", value = "true"),
        @QueryHint(name = "org.hibernate.readOnly", value = "true")})
public final class Host {
  static final Host DEFAULT = new Host();
  private static final Logger logger = Logger.getLogger(Host.class.getName());

  @Id @GeneratedValue @ExcludeFromJson private int id;
  @ElementCollection @Sort(type = NATURAL) private SortedMap<String, String> properties;
  @ExcludeFromJson private int hash;

  private Host() {
    this.properties = Maps.newTreeMap();
    this.hash = 0;
  }

  private Host(Builder builder) {
    this.properties = Maps.newTreeMap(builder.properties);
    this.hash = builder.hashFunction.hashObject(this, HostFunnel.INSTANCE).asInt();
  }

  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.hash == that.hash)
          && this.properties.equals(that.properties);
    } else {
      return false;
    }
  }

  @Override public int hashCode() {
    return hash;
  }

  @Override public String toString() {
    return Objects.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);
    }
  }
}
caliper/src/main/java/com/google/caliper/bridge/WorkerSpec.java
public final class WorkerSpec {
  public final String workerClassName;
  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<String> methodParameterClassNames;
  public final int port;

  public WorkerSpec(
      String workerClassName,
      ImmutableMap<String, String> workerOptions,
      BenchmarkSpec benchmarkSpec,
      ImmutableList<String> methodParameterClassNames,
      int port) {
    this.workerClassName = workerClassName;
    this.workerOptions = workerOptions;
    this.benchmarkSpec = benchmarkSpec;
    this.methodParameterClassNames = methodParameterClassNames;
    this.port = port;
  }
}
caliper/src/main/java/com/google/caliper/bridge/CaliperControlLogMessage.java
public abstract class CaliperControlLogMessage extends LogMessage {
  static final String SEPARATOR = "//";
  static final Splitter CONTROL_TYPE_SPLITTER = Splitter.on(SEPARATOR).limit(2);
  static final String CONTROL_PREFIX = SEPARATOR + "\u0279\u01DDd\u0131\u0283\u0250\u2183"
      + SEPARATOR;

  CaliperControlLogMessage() {}
}
caliper/src/main/java/com/google/caliper/model/Scenario.java
@Entity
@Access(FIELD)
@Immutable
@Cacheable
@NamedQuery(
    name = "listScenariosForHash",
    query = "SELECT s FROM Scenario s WHERE hash = :hash",
    hints = {
        @QueryHint(name = "org.hibernate.cacheable", value = "true"),
        @QueryHint(name = "org.hibernate.readOnly", value = "true")})
public final class Scenario {
  static final Scenario DEFAULT = new Scenario();

  @Id @GeneratedValue @ExcludeFromJson private int id;
  @ManyToOne(optional = false) private Host host;
  @ManyToOne(optional = false) private VmSpec vmSpec;
  @ManyToOne(optional = false) 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;
    this.hash = 0;
  }

  private Scenario(Builder builder) {
    this.host = builder.host;
    this.vmSpec = builder.vmSpec;
    this.benchmarkSpec = builder.benchmarkSpec;
    this.hash = getPersistentHashFunction()
        .newHasher()
        .putObject(host, HostFunnel.INSTANCE)
        .putObject(vmSpec, VmSpecFunnel.INSTANCE)
        .putObject(benchmarkSpec, BenchmarkSpecFunnel.INSTANCE)
        .hash()
        .asInt();
  }

  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.hash == that.hash)
          && this.host.equals(that.host)
          && this.vmSpec.equals(that.vmSpec)
          && this.benchmarkSpec.equals(that.benchmarkSpec);
    } else {
      return false;
    }
  }

  @Override public int hashCode() {
    return hash;
  }

  @Override public String toString() {
    return Objects.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);
    }
  }
}
caliper/src/main/java/com/google/caliper/worker/WorkerMain.java
public final class WorkerMain {
  private WorkerMain() {}

  public static void main(String[] args) throws Exception {
    Injector gsonInjector = Guice.createInjector(PRODUCTION, new GsonModule());
    WorkerSpec request =
        gsonInjector.getInstance(Gson.class).fromJson(args[0], WorkerSpec.class);

    Injector workerInjector = gsonInjector.createChildInjector(
        new BenchmarkClassModule(Class.forName(request.benchmarkSpec.className())),
        ExperimentModule.forWorkerSpec(request),
        new BridgeModule(),
        new WorkerModule(request));

    Worker worker = workerInjector.getInstance(Worker.class);
    WorkerEventLog log = workerInjector.getInstance(WorkerEventLog.class);

    log.notifyWorkerStarted();
    try {
      worker.setUpBenchmark();
      log.notifyWarmupPhaseStarting();
      worker.bootstrap();
      log.notifyMeasurementPhaseStarting();
      // TODO(lukes): add a cooperative shutdown mechanism to ensure that we at least try to 
      // shutdown cleanly.  Currently we loop forever and the runner eventually sends a KILL signal.
      while (true) {
        worker.preMeasure();
        log.notifyMeasurementStarting();
        try {
          log.notifyMeasurementEnding(worker.measure());
        } finally {
          worker.postMeasure();
        }
      }
    } catch (Exception e) {
      log.notifyFailure(e);
    } finally {
      System.out.flush(); // ?
      worker.tearDownBenchmark();
    }
  }
}
caliper/src/main/java/com/google/caliper/model/VmSpec.java
@Entity
@Access(FIELD)
@Immutable
@Cacheable
@NamedQuery(
    name = "listVmSpecsForHash",
    query = "SELECT v FROM VmSpec v WHERE hash = :hash",
    hints = {
        @QueryHint(name = "org.hibernate.cacheable", value = "true"),
        @QueryHint(name = "org.hibernate.readOnly", value = "true")})
public final class VmSpec {
  static final VmSpec DEFAULT = new VmSpec();

  @Id @GeneratedValue @ExcludeFromJson private int id;
  @ElementCollection @Sort(type = NATURAL) private SortedMap<String, String> properties;
  @ElementCollection @Sort(type = NATURAL) private SortedMap<String, String> options;
  @ExcludeFromJson private int hash;

  private VmSpec() {
    this.properties = Maps.newTreeMap();
    this.options = Maps.newTreeMap();
    this.hash = 0;
  }

  private VmSpec(Builder builder) {
    this.properties = Maps.newTreeMap(builder.properties);
    this.options = Maps.newTreeMap(builder.options);
    this.hash = getPersistentHashFunction().hashObject(this, VmSpecFunnel.INSTANCE).asInt();
  }

  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.hash == that.hash)
          && this.properties.equals(that.properties)
          && this.options.equals(that.options);
    } else {
      return false;
    }
  }

  @Override public int hashCode() {
    return hash;
  }

  @Override public String toString() {
    return Objects.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);
    }
  }
}
caliper/src/main/java/com/google/caliper/runner/Instrument.java
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 Objects.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();
}


Question: import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkState;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import com.google.caliper.api.ResultProcessor;
import com.google.caliper.api.SkipThisScenarioException;
import com.google.caliper.bridge.AbstractLogMessageVisitor;
import com.google.caliper.bridge.CaliperControlLogMessage;
import com.google.caliper.bridge.FailureLogMessage;
import com.google.caliper.bridge.LogMessage;
import com.google.caliper.bridge.LogMessageVisitor;
import com.google.caliper.bridge.VmOptionLogMessage;
import com.google.caliper.bridge.VmPropertiesLogMessage;
import com.google.caliper.bridge.WorkerSpec;
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.model.VmSpec;
import com.google.caliper.options.CaliperOptions;
import com.google.caliper.runner.Instrument.Instrumentation;
import com.google.caliper.runner.Instrument.MeasurementCollectingVisitor;
import com.google.caliper.util.Parser;
import com.google.caliper.util.ShortDuration;
import com.google.caliper.util.Stderr;
import com.google.caliper.util.Stdout;
import com.google.caliper.worker.WorkerMain;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
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.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.io.Closeables;
import com.google.common.io.LineReader;
import com.google.common.util.concurrent.FutureCallback;
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.ThreadFactoryBuilder;
import com.google.gson.Gson;
import com.google.inject.CreationException;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.ProvisionException;
import com.google.inject.spi.Message;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.ServerSocket;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/*
 * 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;





/**
 * 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 final Injector injector;
  private final CaliperOptions options;
  private final PrintWriter stdout;
  private final PrintWriter stderr;
  private final BenchmarkClass benchmarkClass;
  private final ImmutableSet<Instrument> instruments;
  private final ImmutableSet<ResultProcessor> resultProcessors;
  private final Parser<LogMessage> logMessageParser;
  private final ExperimentSelector selector;
  private final Host host;
  private final Run run;
  private final Gson gson;
  private final ListeningExecutorService consumerExecutor =
      MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
          new ThreadFactoryBuilder()
              .setNameFormat("line-processor-%d")
              .setDaemon(true)
              .build()));
  private final ListeningExecutorService processExecutor =
      MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
          new ThreadFactoryBuilder()
              .setNameFormat("process-watcher-%d")
              .setDaemon(true)
              .build()));

  private final Stopwatch trialStopwatch = new Stopwatch();
  /** This is 1-indexed because it's only used for display to users.  E.g. "Trial 1 of 27" */
  private volatile int trialNumber = 1;

  @Inject @VisibleForTesting
  public ExperimentingCaliperRun(
      Injector injector,
      CaliperOptions options,
      @Stdout PrintWriter stdout,
      @Stderr PrintWriter stderr,
      BenchmarkClass benchmarkClass,
      ImmutableSet<Instrument> instruments,
      ImmutableSet<ResultProcessor> resultProcessors,
      Parser<LogMessage> logMessageParser,
      ExperimentSelector selector,
      Host host,
      Run run,
      Gson gson) {
    this.injector = injector;
    this.options = options;
    this.stdout = stdout;
    this.stderr = stderr;
    this.benchmarkClass = benchmarkClass;
    this.instruments = instruments;
    this.resultProcessors = resultProcessors;
    this.logMessageParser = logMessageParser;
    this.selector = selector;
    this.host = host;
    this.run = run;
    this.gson = gson;
  }

  @Override
  public void run() throws InvalidBenchmarkException {
    stdout.println("Experiment selection: ");
    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();

    ImmutableSet<Experiment> allExperiments = selector.selectExperiments();
    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 = new Stopwatch().start();

    try {
      for (int i = 0; i < options.trialsPerScenario(); i++) {
        for (Experiment experiment : experimentsToRun) {
          stdout.printf("Starting experiment %d of %d: %s%n", trialNumber, totalTrials, experiment);
          try {
            Trial trial = measure(experiment);
            stdout.println("Complete!");
            for (ResultProcessor resultProcessor : resultProcessors) {
              resultProcessor.processTrial(trial);
            }
          } catch (TrialFailureException e) {
            stderr.println(
                "ERROR: Trial failed to complete (its results will not be included in the run):\n"
                    + "  " + e.getMessage());
          } catch (IOException e) {
            throw Throwables.propagate(e);
          } finally {
            trialNumber++;
          }
        }
      }
    } finally {
      consumerExecutor.shutdown();
    }

    stdout.print("\n");
    stdout.format("Execution complete: %s.%n",
        ShortDuration.of(stopwatch.stop().elapsed(NANOSECONDS), NANOSECONDS));

    for (ResultProcessor resultProcessor : resultProcessors) {
      try {
        resultProcessor.close();
      } catch (IOException e) {
        logger.log(WARNING, "Could not close a result processor: " + resultProcessor, e);
      }
    }
  }

  private static final int NUM_WORKER_STREAMS = 3;

  private ProcessBuilder createWorkerProcessBuilder(Experiment experiment,
      BenchmarkSpec benchmarkSpec, int port) {
    Instrumentation instrumentation = experiment.instrumentation();
    Instrument instrument = instrumentation.instrument();
    ImmutableList.Builder<String> parameterClassNames = ImmutableList.builder();
    for (Class<?> parameterType : instrumentation.benchmarkMethod.getParameterTypes()) {
      parameterClassNames.add(parameterType.getName());
    }
    WorkerSpec request = new WorkerSpec(
        instrumentation.workerClass().getName(),
        instrumentation.workerOptions(),
        benchmarkSpec,
        parameterClassNames.build(),
        port);

    ProcessBuilder processBuilder = new ProcessBuilder().redirectErrorStream(false);

    List<String> args = processBuilder.command();

    String jvmName = experiment.vm().name;

    String jdkPath = experiment.vm().config.javaExecutable().getAbsolutePath();
    args.add(jdkPath);
    logger.fine(String.format("Java(%s) Path: %s", jvmName, jdkPath));

    ImmutableList<String> jvmOptions = experiment.vm().config.options();
    args.addAll(jvmOptions);
    logger.fine(String.format("Java(%s) args: %s", jvmName, jvmOptions));

    ImmutableSet<String> benchmarkJvmOptions = benchmarkClass.vmOptions();
    args.addAll(benchmarkJvmOptions);
    logger.fine(String.format("Benchmark(%s) Java args: %s", benchmarkClass.name(),
        benchmarkJvmOptions));

    String classPath = getClassPath();
    Collections.addAll(args, "-cp", classPath);
    logger.finer(String.format("Class path: %s", classPath));

    Iterable<String> instrumentJvmOptions = instrument.getExtraCommandLineArgs();
    Iterables.addAll(args, instrumentJvmOptions);
    logger.fine(String.format("Instrument(%s) Java args: %s", instrument.getClass().getName(),
        instrumentJvmOptions));

    // last to ensure that they're always applied
    args.add("-XX:+PrintFlagsFinal");
    args.add("-XX:+PrintCompilation");
    args.add("-XX:+PrintGC");

    args.add(WorkerMain.class.getName());
    args.add(gson.toJson(request));

    logger.finest(String.format("Full JVM (%s) args: %s", jvmName, args));
    return processBuilder;
  }

  private static String getClassPath() {
    String classpath;
    // Use the effective class path in case this is being invoked in an isolated class loader
    classpath =
        EffectiveClassPath.getClassPathForClassLoader(Thread.currentThread().getContextClassLoader());
    return classpath;
  }


  private Trial measure(Experiment experiment) throws IOException {
    BenchmarkSpec benchmarkSpec = new BenchmarkSpec.Builder()
        .className(experiment.instrumentation().benchmarkMethod().getDeclaringClass().getName())
        .methodName(experiment.instrumentation().benchmarkMethod().getName())
        .addAllParameters(experiment.userParameters())
        .build();

    final ServerSocket serverSocket = new ServerSocket(0);

    final WorkerProcess process =
        new WorkerProcess(createWorkerProcessBuilder(experiment, benchmarkSpec,
            serverSocket.getLocalPort()));
    ListenableFuture<Integer> processFuture = processExecutor.submit(new Callable<Integer>() {
      @Override public Integer call() throws Exception {
        return process.waitFor();
      }
    });

    trialStopwatch.start();

    final ListeningExecutorService producerExecutor = MoreExecutors.listeningDecorator(
        Executors.newFixedThreadPool(NUM_WORKER_STREAMS, new ThreadFactoryBuilder()
            .setDaemon(true)
            .setNameFormat("stream-listener-%d")
            .build()));
    try {
      final BlockingQueue<String> queue = Queues.newLinkedBlockingQueue();

      // use the default charset because worker streams will use the default for output
      Charset processCharset = Charset.defaultCharset();
      ListenableFuture<Void> inputFuture = producerExecutor.submit(
          new LineProducer(new InputStreamReader(process.getInputStream(), processCharset), queue));
      ListenableFuture<Void> errorFuture = producerExecutor.submit(
          new LineProducer(new InputStreamReader(process.getErrorStream(), processCharset), queue));
      final ListenableFuture<Reader> pipeReaderFuture =
          producerExecutor.submit(new Callable<Reader>() {
        @Override public Reader call() throws IOException {
          return new InputStreamReader(serverSocket.accept().getInputStream(), UTF_8);
        }
      });
      processFuture.addListener(new Runnable() {
        @Override public void run() {
          if (!pipeReaderFuture.isDone()) {
            // the process completed without the pipe ever being written to.  it crashed.
            // TODO(gak): get the output from the worker so we can know why it crashed
            stdout.print("The worker exited without producing data. "
                + "It has likely crashed. Run with --verbose to see any worker output.\n");
            stdout.flush();
            System.exit(1);
          }
        }
      }, MoreExecutors.sameThreadExecutor());
      Futures.addCallback(pipeReaderFuture, new FutureCallback<Reader>() {
        @Override public void onSuccess(Reader result) {
          logger.fine("successfully opened the pipe from the worker");
          producerExecutor.submit(new LineProducer(result, queue));
        }

        @Override public void onFailure(Throwable t) {
          logger.log(SEVERE, "Could not open the pipe from the worker", t);
          // don't worry about propagating the exception since the future will take care of it
        }
      });

      MeasurementCollectingVisitor measurementCollectingVisitor =
          experiment.instrumentation().getMeasurementCollectingVisitor();
      DataCollectingVisitor dataCollectingVisitor = new DataCollectingVisitor();

      /*
       * Start watching the queue before we wait on the pipe so that we can see output from failed
       * workers.
       */
      ListenableFuture<Void> consumerFuture = consumerExecutor.submit(
          new LineConsumer(queue, measurementCollectingVisitor,
              ImmutableSet.of(dataCollectingVisitor)));
      consumerFuture.addListener(new Runnable() {
        @Override public void run() {
          // kill the process because we're all done
          // TODO(gak): send SIGINT instead of SIGTERM for clean exit
          process.destroy();
        }
      }, MoreExecutors.sameThreadExecutor());

      process.waitFor();
      consumerFuture.get();

      ImmutableMap<String, String> vmOptions = dataCollectingVisitor.vmOptionsBuilder.build();
      checkState(!vmOptions.isEmpty());
      VmSpec vmSpec = new VmSpec.Builder()
          .addAllProperties(dataCollectingVisitor.vmProperties.get())
          .addAllOptions(vmOptions)
          .build();
      return new Trial.Builder(UUID.randomUUID())
          .run(run)
          .instrumentSpec(experiment.instrumentation().instrument().getSpec())
          .scenario(new Scenario.Builder()
              .host(host)
              .vmSpec(vmSpec)
              .benchmarkSpec(benchmarkSpec))
          .addAllMeasurements(measurementCollectingVisitor.getMeasurements())
          .build();
    } catch (InterruptedException e) {
      throw new AssertionError();
    } catch (ExecutionException e) {
      Throwable cause = e.getCause();
      Throwables.propagateIfInstanceOf(cause, TrialFailureException.class);
      throw Throwables.propagate(cause);
    } finally {
      trialStopwatch.reset();
      producerExecutor.shutdownNow();
      serverSocket.close();
    }
  }

  private long getRemainingTrialNanos() {
    ShortDuration timeLimit = options.timeLimit();
    if (ShortDuration.zero().equals(timeLimit)) {
      return Long.MAX_VALUE;
    }
    return timeLimit.to(NANOSECONDS) - trialStopwatch.elapsed(NANOSECONDS);
  }

  /**
   * 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) {
      Class<?> clazz = benchmarkClass.benchmarkClass();
      try {
        Object benchmark = injector.createChildInjector(ExperimentModule.forExperiment(experiment))
            .getInstance(Key.get(clazz));
        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 (ProvisionException e) {
        Throwable cause = e.getCause();
        if (cause != null) {
          throw new UserCodeException(cause);
        }
        throw e;
      } catch (CreationException e) {
        // Guice formatting is a little ugly
        StringBuilder message = new StringBuilder(
            "Could not create an instance of the benchmark class following reasons:");
        int errorNum = 0;
        for (Message guiceMessage : e.getErrorMessages()) {
          message.append("\n  ").append(++errorNum).append(") ")
              .append(guiceMessage.getMessage());;
        }
        throw new InvalidBenchmarkException(message.toString(), e);
      } catch (SkipThisScenarioException innocuous) {}
    }
    return builder.build();
  }

  private static final class DataCollectingVisitor extends AbstractLogMessageVisitor {
    final ImmutableMap.Builder<String, String> vmOptionsBuilder = ImmutableMap.builder();
    Optional<ImmutableMap<String, String>> vmProperties = Optional.absent();

    @Override
Answer:     public void visit(FailureLogMessage logMessage) {