Split up compile server into multiple parts.

  - A simple server that accepts zip files containing benchmarks.
  - Several command line applications that run and compile benchmarks.

Change-Id: Ica402ac9318c114e17f7146bb84ce6e97116b16d
diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
index 766055f..81e703f 100644
--- a/benchmarks/pom.xml
+++ b/benchmarks/pom.xml
@@ -17,8 +17,6 @@
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <!-- Using an older version of GWT here allows the benchmarks to execute against old      -->
-    <!-- versions of the GWT SDK. This will help to find old commits that changed performance -->
     <gwtversion>2.7.0</gwtversion>
   </properties>
 
@@ -37,13 +35,11 @@
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
-      <version>4.11</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>commons-io</groupId>
       <artifactId>commons-io</artifactId>
-      <version>2.4</version>
       </dependency>
   </dependencies>
 
@@ -63,44 +59,16 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>2.3</version>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
       </plugin>
 
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
-        <version>2.5.1</version>
         <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.7</source>
-          <target>1.7</target>
-        </configuration>
       </plugin>
 
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-eclipse-plugin</artifactId>
-        <version>2.8</version>
-        <configuration>
-          <downloadSources>true</downloadSources>
-          <downloadJavadocs>false</downloadJavadocs>
-          <projectnatures>
-            <projectnature>org.eclipse.jdt.core.javanature</projectnature>
-          </projectnatures>
-          <buildcommands>
-            <buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
-          </buildcommands>
-          <classpathContainers>
-            <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
-          </classpathContainers>
-        </configuration>
       </plugin>
 
       <plugin>
diff --git a/benchmarks/src/main/scripts/compileModule b/benchmarks/src/main/scripts/compileModule
index 1333172..0273ca2 100755
--- a/benchmarks/src/main/scripts/compileModule
+++ b/benchmarks/src/main/scripts/compileModule
@@ -10,10 +10,15 @@
 
 
 MODULE_NAME=${1}
-GWT_DEV_JAR=${2}
-GWT_USER_JAR=${3}
-BENCHMARKS_SRC=${4}
-OUTPUT_DIR=${5}
+shift
+GWT_DEV_JAR=${1}
+shift
+GWT_USER_JAR=${1}
+shift
+BENCHMARKS_SRC=${1}
+shift
+OUTPUT_DIR=${1}
+shift
 
 java -Dgwt.persistentunitcache=false -cp ${GWT_DEV_JAR}:${GWT_USER_JAR}:${BENCHMARKS_SRC} \
-    com.google.gwt.dev.Compiler -style PRETTY -war ${OUTPUT_DIR} "${MODULE_NAME}"
\ No newline at end of file
+    com.google.gwt.dev.Compiler ${COMPILER_EXTRA_ARGS} -war ${OUTPUT_DIR} ${@} ${MODULE_NAME}
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..7b96831
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,6 @@
+#!/bin/bash -e
+
+echo "Starting build"
+mvn clean install
+echo "Build finished successfully"
+
diff --git a/cli/pom.xml b/cli/pom.xml
new file mode 100644
index 0000000..75ec0fc
--- /dev/null
+++ b/cli/pom.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.google.gwt.benchmark</groupId>
+  <artifactId>gwt-benchmark-cli</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <parent>
+    <groupId>com.google.gwt.benchmark</groupId>
+    <artifactId>gwt-benchmark-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.gwt.benchmark</groupId>
+      <artifactId>gwt-benchmark-common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.mail</groupId>
+      <artifactId>mail</artifactId>
+      <version>1.4</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.api-client</groupId>
+      <artifactId>google-api-client-jackson2</artifactId>
+      <version>1.20.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.api-client</groupId>
+      <artifactId>google-api-client</artifactId>
+      <version>1.20.0</version>
+      <exclusions>
+        <exclusion>
+          <groupId>com.google.guava</groupId>
+          <artifactId>guava-jdk5</artifactId>
+        </exclusion>
+     </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>com.google.oauth-client</groupId>
+      <artifactId>google-oauth-client</artifactId>
+      <version>1.20.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.oauth-client</groupId>
+      <artifactId>google-oauth-client-java6</artifactId>
+      <version>1.20.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.gdata</groupId>
+      <artifactId>core</artifactId>
+      <version>1.47.1</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>args4j</groupId>
+      <artifactId>args4j</artifactId>
+      <version>2.32</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.inject</groupId>
+      <artifactId>guice</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.inject.extensions</groupId>
+      <artifactId>guice-assistedinject</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpmime</artifactId>
+      <version>4.5</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-eclipse-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
+
diff --git a/cli/run_system.sh b/cli/run_system.sh
new file mode 100755
index 0000000..a4f9270
--- /dev/null
+++ b/cli/run_system.sh
@@ -0,0 +1,8 @@
+#!/bin/bash -e
+
+echo "Assuming build.sh ran successfully!"
+
+quoted_args="$(printf " %q" "$@")"
+
+mvn exec:java -Dexec.mainClass=com.google.j2cl.benchmark.cli.Cli -Dexec.args="$quoted_args"
+
diff --git a/cli/run_zip.sh b/cli/run_zip.sh
new file mode 100755
index 0000000..2836fcc
--- /dev/null
+++ b/cli/run_zip.sh
@@ -0,0 +1,8 @@
+#!/bin/bash -e
+
+echo "Assuming build.sh ran successfully!"
+
+quoted_args="$(printf " %q" "$@")"
+
+mvn exec:java -Dexec.mainClass=com.google.j2cl.benchmark.cli.RunZip -Dexec.args="$quoted_args"
+
diff --git a/compileserver/config/runner_template.html b/cli/runner_template.html
similarity index 99%
rename from compileserver/config/runner_template.html
rename to cli/runner_template.html
index 1f0fdba..a5a2af7 100644
--- a/compileserver/config/runner_template.html
+++ b/cli/runner_template.html
@@ -8,3 +8,4 @@
   <body>
   </body>
 </html>
+
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkCompiler.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkCompiler.java
similarity index 87%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkCompiler.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkCompiler.java
index 4d5e02e..faa19a4 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkCompiler.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkCompiler.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
 
 import java.io.File;
 
@@ -22,5 +22,6 @@
   /**
    * Invokes the GWT compiler for the specified module.
    */
-  public void compile(String moduleName, File outputDir, File devJar, File userJar) throws BenchmarkCompilerException;
+  public void compile(String moduleName, File outputDir, File devJar, File userJar,
+      String extraArgs) throws CliException;
 }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkCompilerException.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkCompilerException.java
similarity index 93%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkCompilerException.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkCompilerException.java
index c9c199a..ffb6947 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkCompilerException.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkCompilerException.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
 
 /**
  * This exception is thrown if a benchmark compile fails for some reason.
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkFinder.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkFinder.java
similarity index 97%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkFinder.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkFinder.java
index bc68f0c..1dbbf54 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkFinder.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkFinder.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
 
 import com.google.inject.name.Named;
 
diff --git a/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkModule.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkModule.java
new file mode 100644
index 0000000..7a61cd2
--- /dev/null
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkModule.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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.j2cl.benchmark.cli;
+
+import com.google.common.base.Predicate;
+import com.google.common.io.Files;
+import com.google.gdata.client.spreadsheet.SpreadsheetService;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+import com.google.j2cl.benchmark.cli.MailReporter.MailHelper;
+import com.google.j2cl.benchmark.cli.MailReporter.MailHelperProdImpl;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * The compile server guice module.
+ */
+public class BenchmarkModule extends AbstractModule {
+
+  private final Cli cli;
+
+  public BenchmarkModule(Cli cli) {
+    this.cli = cli;
+  }
+
+  @Override
+  protected void configure() {
+    bind(BenchmarkCompiler.class).to(CliInteractor.class);
+    install(new FactoryModuleBuilder().build(BenchmarkWorker.Factory.class));
+    install(new FactoryModuleBuilder().build(BenchmarkReporter.Factory.class));
+    install(new FactoryModuleBuilder().build(BenchmarkUploader.Factory.class));
+    bind(MailHelper.class).to(MailHelperProdImpl.class);
+    bind(String.class).annotatedWith(Names.named("compilerArgs")).toInstance(cli.compilerArgs);
+    bind(String.class).annotatedWith(Names.named("randomStringProvider"))
+        .toProvider(RandomStringProvider.class);
+
+
+    final File tempDir = Files.createTempDir();
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+
+      @Override
+      public void run() {
+        try {
+          FileUtils.deleteDirectory(tempDir);
+        } catch (IOException ignored) {
+        }
+      }
+    });
+
+    bind(File.class).annotatedWith(Names.named("compilerOutputDir"))
+        .toInstance(tempDir);
+
+    bind(Integer.class).annotatedWith(Names.named("poolSize"))
+        .toInstance(cli.threadPoolSize);
+    bind(File.class).annotatedWith(Names.named("benchmarkSourceLocation"))
+        .toInstance(new File(cli.benchmarksLocation, "src/main/java/"));
+    bind(ExecutorService.class).annotatedWith(Names.named("managerPoolSize"))
+        .toProvider(PoolProvider.class);
+    bind(String.class).annotatedWith(Names.named("moduleTemplate"))
+        .toInstance(cli.moduleTemplateContent);
+    bind(String.class).annotatedWith(Names.named("runServerUrl"))
+    .toInstance(cli.runServer);
+    bind(new TypeLiteral<Predicate<String>>(){})
+        .annotatedWith(Names.named("benchmarkFilter"))
+        .toInstance(cli.benchmarkFilter);
+    bind(Boolean.class).annotatedWith(Names.named("deamonMode")).toInstance(cli.deamon);
+    bind(Boolean.class).annotatedWith(Names.named("skipSDKBuild")).toInstance(cli.skipBuild);
+
+    bind(File.class).annotatedWith(Names.named("scriptDirectory"))
+        .toInstance(new File(cli.benchmarksLocation, "src/main/scripts/"));
+    bind(Boolean.class).annotatedWith(Names.named("reportResults"))
+        .toInstance(cli.reportResults);
+    bind(Boolean.class).annotatedWith(Names.named("reportErrors"))
+    .toInstance(cli.reportErrors);
+    bind(File.class).annotatedWith(Names.named("persistenceDir"))
+        .toInstance(cli.persistenceDir);
+    bind(File.class).annotatedWith(Names.named("gwtSourceLocation"))
+        .toInstance(cli.gwtSourceLocation);
+    bind(File.class).annotatedWith(Names.named("gwtDevJar"))
+    .toInstance(cli.gwtDevJar);
+    bind(File.class).annotatedWith(Names.named("gwtUserJar"))
+    .toInstance(cli.gwtUserJar);
+
+    bind(MailSettings.class).toInstance(new MailSettings(cli.mailFrom, cli.mailTo, cli.mailHost, cli.mailUsername, cli.mailPassword));
+    bind(String.class).annotatedWith(Names.named("spreadSheetId")).toInstance(
+        cli.spreadsheetId);
+    bind(String.class).annotatedWith(Names.named("client_json_secret")).toInstance(
+        cli.oauthSecretContent);
+  }
+
+  @Provides
+  public SpreadsheetService spreadSheetService() {
+    return new SpreadsheetService("benchmark");
+  }
+
+  private static class PoolProvider implements Provider<ExecutorService> {
+
+    private int poolSize;
+
+    @Inject
+    public PoolProvider(@Named("poolSize") int poolSize) {
+      this.poolSize = poolSize;
+    }
+
+    @Override
+    public ExecutorService get() {
+      return Executors.newFixedThreadPool(poolSize);
+    }
+  }
+
+  private static class RandomStringProvider implements Provider<String> {
+    @Override
+    public String get() {
+      return UUID.randomUUID().toString();
+    }
+  }
+}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporter.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkReporter.java
similarity index 84%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporter.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkReporter.java
index 7eee209..1e6dc75 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporter.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkReporter.java
@@ -11,13 +11,14 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
 
 import com.google.api.client.auth.oauth2.Credential;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.gdata.client.spreadsheet.FeedURLFactory;
 import com.google.gdata.client.spreadsheet.SpreadsheetService;
 import com.google.gdata.data.PlainTextConstruct;
@@ -26,12 +27,12 @@
 import com.google.gdata.data.spreadsheet.WorksheetEntry;
 import com.google.gdata.data.spreadsheet.WorksheetFeed;
 import com.google.gdata.util.ServiceException;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkRun.Result;
-import com.google.gwt.benchmark.oauth2.server.OAuthHelper;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.name.Named;
+import com.google.j2cl.benchmark.cli.BenchmarkRun.Result;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
 
 import java.io.File;
 import java.io.IOException;
@@ -40,6 +41,8 @@
 import java.net.URL;
 import java.security.GeneralSecurityException;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -53,19 +56,13 @@
  * for the current commit id in the spreadsheet and will update a row, this means that even if the
  * reporter failed one update the data will be consitent after the next upload.
  */
-public class BenchmarkReporter implements Runnable {
+public class BenchmarkReporter {
 
   private static final String COMMIT_HEADER = "commit";
 
-  public interface ReportProgressHandler {
-    void onCommitReported();
-
-    void onPermanentFailure();
-  }
-
   public interface Factory {
-    BenchmarkReporter create(Map<String, BenchmarkRun> results,
-        @Assisted("commitId") String commitId, ReportProgressHandler reportProgressHandler);
+    BenchmarkReporter create(List<BenchmarkRun> results,
+        @Assisted("commitId") String commitId);
   }
 
   private static class ValueToAdd {
@@ -79,11 +76,9 @@
 
   private static Logger logger = Logger.getLogger(BenchmarkReporter.class.getName());
 
-  private final Map<String, BenchmarkRun> benchmarkRunsByBenchmarkName;
+  private final List<BenchmarkRun> benchmarkRuns;
   private final String commitId;
 
-  private final ReportProgressHandler reportProgressHandler;
-
   private final File oAuthDir;
 
   private final String clientJsonSecret;
@@ -93,26 +88,23 @@
   private final String spreadSheetId;
 
   @Inject
-  public BenchmarkReporter(@Assisted Map<String, BenchmarkRun> benchmarkRunsByBenchmarkName,
-      @Assisted("commitId") String commitId, @Assisted ReportProgressHandler reportProgressHandler,
+  public BenchmarkReporter(@Assisted List<BenchmarkRun> benchmarkRunsByBenchmarkName,
+      @Assisted("commitId") String commitId,
       @Named("persistenceDir") File oAuthDir, @Named("client_json_secret") String clientJsonSecret,
       Provider<SpreadsheetService> spreadSheetServiveProvider,
       @Named("spreadSheetId") String spreadSheetId) {
-    this.benchmarkRunsByBenchmarkName = benchmarkRunsByBenchmarkName;
+    this.benchmarkRuns = benchmarkRunsByBenchmarkName;
     this.commitId = commitId;
-    this.reportProgressHandler = reportProgressHandler;
     this.oAuthDir = oAuthDir;
     this.clientJsonSecret = clientJsonSecret;
     this.spreadSheetServiveProvider = spreadSheetServiveProvider;
     this.spreadSheetId = spreadSheetId;
   }
 
-  @Override
-  public void run() {
+  public boolean report() {
     for (int delay : WAITING_TIME_SECONDS) {
       if (postResultToServer()) {
-        reportProgressHandler.onCommitReported();
-        return;
+        return true;
       }
       logger.warning(String.format("Could not post results to dashboard retrying in %d seconds.",
           delay));
@@ -120,7 +112,7 @@
         break;
       }
     }
-    reportProgressHandler.onPermanentFailure();
+    return false;
   }
 
   private Map<String, WorksheetEntry> sheetsByName(SpreadsheetService service) throws IOException,
@@ -156,7 +148,13 @@
     Map<String, WorksheetEntry> sheetsByName = sheetsByName(service);
 
     // calculate all sheets that need to be created
-    Set<String> sheetsToCreate = Sets.newHashSet(benchmarkRunsByBenchmarkName.keySet());
+    Set<String> sheetsToCreate = new HashSet<>(FluentIterable.from(benchmarkRuns).transform(
+        new Function<BenchmarkRun, String>() {
+            @Override
+            public String apply(BenchmarkRun input) {
+              return getNameFromBenchmarkRun(input);
+            }
+        }).toSet());
     Set<String> allSpreadSheetNames = sheetsByName.keySet();
     sheetsToCreate.removeAll(allSpreadSheetNames);
 
@@ -164,13 +162,17 @@
     sheetsByName.putAll(createdWorkSheetsByName);
 
 
-    List<String> benchmarkNamesList = Lists.newArrayList(benchmarkRunsByBenchmarkName.keySet());
+    List<BenchmarkRun> benchmarkNamesList = Lists.newArrayList(benchmarkRuns);
     // make sure our order of adding the spreadsheets is deterministic (makes testing easier)
-    Collections.sort(benchmarkNamesList);
+    Collections.sort(benchmarkNamesList, new Comparator<BenchmarkRun>() {
+      @Override
+      public int compare(BenchmarkRun o1, BenchmarkRun o2) {
+        return o1.getModuleName().compareTo(o2.getModuleName());
+      }
+    });
 
-    for (String benchmarkName : benchmarkNamesList) {
-      BenchmarkRun benchmarkRun = benchmarkRunsByBenchmarkName.get(benchmarkName);
-      WorksheetEntry worksheetEntry = sheetsByName.get(benchmarkName);
+    for (BenchmarkRun benchmarkRun  : benchmarkNamesList) {
+      WorksheetEntry worksheetEntry = sheetsByName.get(getNameFromBenchmarkRun(benchmarkRun));
       if (worksheetEntry == null) {
         // This should never happen since we just created the damn thing
         throw new IllegalStateException();
@@ -179,8 +181,17 @@
     }
   }
 
-  private void postBenchmarkRunToSpreadSheet(SpreadsheetService service, WorksheetEntry worksheetEntry,
-      BenchmarkRun benchmarkRun) throws Exception {
+  private String getNameFromBenchmarkRun(BenchmarkRun run) {
+    if (!run.getModuleName().startsWith("com.google.gwt.benchmark.benchmarks.")) {
+      throw new IllegalStateException(
+          "Uploader is assuming all benchmarks reside in the same package");
+    }
+    return run.getModuleName().substring("com.google.gwt.benchmark.benchmarks.".length()) + "_"
+        + run.getReportingName();
+  }
+
+  private void postBenchmarkRunToSpreadSheet(SpreadsheetService service,
+      WorksheetEntry worksheetEntry, BenchmarkRun benchmarkRun) throws Exception {
 
     logger.info(String.format("Updating spreadsheet with commit %s for benchmark %s", commitId,
         benchmarkRun.getModuleName()));
@@ -204,6 +215,15 @@
     }
     List<ValueToAdd> valuesToAdd = calculateValuesToAdd(headers, benchmarkRun, rowIndex);
     addOrUpdateCells(service, worksheetEntry, valuesToAdd);
+
+    sleepBecauseOfBug();
+  }
+
+  @VisibleForTesting
+  void sleepBecauseOfBug() throws InterruptedException {
+    // TODO(dankurka): follow up with docs teams
+    //see http://b/21856597
+    Thread.sleep(5000);
   }
 
   private void ensureAllHeadersPresent(SpreadsheetService service, WorksheetEntry worksheetEntry,
@@ -364,7 +384,7 @@
     for (String name : sortedSheetsToCreate) {
       WorksheetEntry entry = new WorksheetEntry();
       entry.setTitle(new PlainTextConstruct(name));
-      entry.setRowCount(10000);
+      entry.setRowCount(1000);
       entry.setColCount(10);
       map.put(name, feed.insert(entry));
     }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkRun.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkRun.java
similarity index 70%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkRun.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkRun.java
index 5b5ee4f..fc12c52 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkRun.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkRun.java
@@ -1,8 +1,22 @@
-package com.google.gwt.benchmark.compileserver.server.manager;
+/*
+ * Copyright 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.j2cl.benchmark.cli;
+
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -21,20 +35,12 @@
    */
   public static class Result {
 
-    public static Result copyOf(Result other) {
-      Result result = new Result();
-      result.runsPerSecond = other.runsPerSecond;
-      result.state = other.state;
-      result.errorMessage = other.errorMessage;
-      return result;
-    }
-
     private State state;
     private double runsPerSecond;
     private String errorMessage;
 
     public enum State {
-      NOT_RUN, FAILED_RUN, DONE
+      NOT_RUN, FAILED_RUN, SUCCESSFUL_RUN
     }
 
     public Result() {
@@ -42,7 +48,7 @@
     }
 
     public void setRunsPerSecond(double runsPerSecond) {
-      state = State.DONE;
+      state = State.SUCCESSFUL_RUN;
       this.runsPerSecond = runsPerSecond;
     }
 
@@ -55,6 +61,7 @@
     }
 
     public void setErrorMessage(String errorMessage) {
+      state = State.FAILED_RUN;
       this.errorMessage = errorMessage;
     }
 
@@ -64,25 +71,8 @@
   }
 
   public enum State {
-    NOT_RUN, COMPILING, FAILED_COMPILE, FAILED_TO_GENERATE_HOST_PAGE, FAILED_TO_RUN_ON_RUNNER,
-    DONE, FAILED_TO_CREATE_DIR,
-  }
-
-  public static BenchmarkRun from(BenchmarkRun other) {
-    BenchmarkRun clone = new BenchmarkRun(other.moduleName, other.commitId, other.commitMsEpoch);
-    clone.runners = other.getRunConfigs();
-    clone.results = deepClone(other.results);
-    clone.state = other.state;
-    clone.errorMessage = other.errorMessage;
-    return clone;
-  }
-
-  private static Map<RunnerConfig, Result> deepClone(Map<RunnerConfig, Result> runMap) {
-    Map<RunnerConfig, Result> map = new HashMap<>();
-    for (Map.Entry<RunnerConfig, Result> entry : runMap.entrySet()) {
-      map.put(entry.getKey(), Result.copyOf(entry.getValue()));
-    }
-    return map;
+    NOT_RUN, FAILED_COMPILE, FAILED_TO_GENERATE_HOST_PAGE, FAILED_TO_RUN_ON_RUNNER,
+    DONE, FAILED_TO_CREATE_DIR, FAILED_TO_ZIP_BENCHMARK,
   }
 
   private State state;
@@ -99,10 +89,13 @@
 
   private final long commitMsEpoch;
 
-  public BenchmarkRun(String moduleName, String commitId, long commitMsEpoch) {
+  private final String reportingName;
+
+  public BenchmarkRun(String moduleName, String commitId, long commitMsEpoch, String reportingName) {
     this.moduleName = moduleName;
     this.commitId = commitId;
     this.commitMsEpoch = commitMsEpoch;
+    this.reportingName = reportingName;
     state = State.NOT_RUN;
   }
 
@@ -158,15 +151,18 @@
   public void setFailedCompile(String message) {
     this.errorMessage = message;
     state = State.FAILED_COMPILE;
+    setResultsToFailed();
   }
 
   public void setFailedHostPageGenerationFailed(String errorMessage) {
     this.errorMessage = errorMessage;
     state = State.FAILED_TO_GENERATE_HOST_PAGE;
+    setResultsToFailed();
   }
 
   public void setFailedToCreateDirectory() {
     state = State.FAILED_TO_CREATE_DIR;
+    setResultsToFailed();
   }
 
   public void setFailedToRunOnServer() {
@@ -176,4 +172,14 @@
   public void setRunEnded() {
     state = State.DONE;
   }
+
+  private void setResultsToFailed() {
+    for (Result r : results.values()) {
+      r.state = Result.State.FAILED_RUN;
+    }
+  }
+
+  public String getReportingName() {
+    return reportingName;
+  }
 }
diff --git a/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkUploader.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkUploader.java
new file mode 100644
index 0000000..97dc073
--- /dev/null
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkUploader.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 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.j2cl.benchmark.cli;
+
+import com.google.api.client.repackaged.com.google.common.base.Joiner;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.inject.assistedinject.Assisted;
+import com.google.j2cl.benchmark.common.runner.Job;
+import com.google.j2cl.benchmark.common.runner.JobId;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigJson;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * BenchmarkUploader uploads a zip file to the lab server and gathers results.
+ */
+public class BenchmarkUploader {
+
+  public interface Factory {
+    BenchmarkUploader create(File zip, List<RunnerConfig> runnerConfigs);
+  }
+
+  private final File zip;
+  private final String serverUrl;
+  private final List<RunnerConfig> runnerConfigs;
+
+  @Inject
+  public BenchmarkUploader(@Named("runServerUrl") String serverUrl, @Assisted File zip,
+      @Assisted List<RunnerConfig> runnerConfigs) {
+    this.zip = zip;
+    this.runnerConfigs = runnerConfigs;
+    if (!serverUrl.endsWith("/")) {
+      serverUrl += "/";
+    }
+    serverUrl += "upload";
+    this.serverUrl = serverUrl;
+  }
+
+  public Job run(boolean verbose) throws IOException, InterruptedException {
+    JobId jobId = sendToWebDriver(zip, runnerConfigs);
+    Job job = getStatus(jobId);
+    while (!job.isDone()) {
+      Thread.sleep(1000l);
+      if (verbose) {
+        System.out.print(".");
+      }
+      job = getStatus(jobId);
+    }
+    if (verbose) {
+      System.out.print("\n");
+    }
+    return job;
+  }
+
+  private Job getStatus(JobId jobId) throws IOException {
+    HttpGet httpGet = new HttpGet(serverUrl + "?jobId=" + jobId.getId());
+    String responseJson = execute(httpGet);
+    JobResponse jobResponse = new GsonBuilder()
+        .registerTypeAdapter(RunnerConfig.class, new RunnerConfigJson())
+        .create()
+        .fromJson(responseJson, JobResponse.class);
+    return jobResponse.job;
+  }
+
+  private JobId sendToWebDriver(File zipFile, List<RunnerConfig> runnerConfigs) throws IOException {
+    String runnerIdsByComma = Joiner.on(",").join(runnerConfigs);
+
+    HttpEntity entity = MultipartEntityBuilder.create()
+        .addTextBody("runnerIds", runnerIdsByComma)
+        .addBinaryBody("file", zipFile, ContentType.create("application/zip"), zipFile.getName())
+        .build();
+
+    HttpPost post = new HttpPost(serverUrl);
+    post.setEntity(entity);
+
+    String responseJson = execute(post);
+    JobIdResponse jobIdResponse = new Gson().fromJson(responseJson, JobIdResponse.class);
+    return jobIdResponse.jobId;
+  }
+
+  private static class JobIdResponse {
+    JobId jobId;
+  }
+
+  private static class JobResponse {
+    Job job;
+  }
+
+  private String execute(HttpUriRequest request) throws IOException {
+    ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
+        @Override
+      public String handleResponse(final HttpResponse response) throws ClientProtocolException,
+          IOException {
+        int status = response.getStatusLine().getStatusCode();
+        if (status >= 200 && status < 300) {
+          HttpEntity entity = response.getEntity();
+          return entity != null ? EntityUtils.toString(entity) : null;
+        } else {
+          throw new ClientProtocolException("Unexpected response status: " + status + " message:"
+              + IOUtils.toString(response.getEntity().getContent()));
+        }
+      }
+    };
+
+    try(CloseableHttpClient httpclient = createHttpClient();) {
+      return httpclient.execute(request, responseHandler);
+    }
+  }
+
+  @VisibleForTesting
+  CloseableHttpClient createHttpClient() {
+    return HttpClients.createDefault();
+  }
+}
diff --git a/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkWorker.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkWorker.java
new file mode 100644
index 0000000..d7e7fec
--- /dev/null
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkWorker.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 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.j2cl.benchmark.cli;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.name.Named;
+import com.google.j2cl.benchmark.cli.BenchmarkRun.State;
+import com.google.j2cl.benchmark.common.runner.Job;
+import com.google.j2cl.benchmark.common.util.ZipUtil;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+import javax.inject.Inject;
+
+/**
+ * BenchmarkWorker compiles a single module, writes the host page and asks a Runner to execute the
+ * benchmark handing back results.
+ */
+public class BenchmarkWorker implements Callable<BenchmarkWorker.WorkResult> {
+
+  public static class WorkResult {
+
+    public WorkResult(State state) {
+      this(state, "");
+    }
+
+    public WorkResult(State state, String reason) {
+      this.state = state;
+      this.job = null;
+      this.reason = reason;
+    }
+
+    public WorkResult(Job job) {
+      this.job = job;
+      this.state = State.DONE;
+      this.reason = null;
+    }
+
+    public final Job job;
+    public final State state;
+    public final String reason;
+
+    public boolean isFailed() {
+      switch(state) {
+        case DONE:
+        case NOT_RUN:
+          return false;
+        default:
+          return true;
+      }
+    }
+  }
+
+  public interface Factory {
+    BenchmarkWorker create(BenchmarkWorkerConfig benchmarkWorkerConfig);
+  }
+
+  private final BenchmarkCompiler compiler;
+  private final BenchmarkWorkerConfig benchmarkData;
+  private final String moduleTemplate;
+  private File compilerOutputDir;
+  private Provider<String> randomStringProvider;
+  private BenchmarkUploader.Factory benchmarkUploderFactory;
+
+  @Inject
+  public BenchmarkWorker(
+      BenchmarkCompiler compiler,
+      @Named("moduleTemplate") String moduleTemplate,
+      @Assisted BenchmarkWorkerConfig benchmarkData,
+      @Named("compilerOutputDir") File compilerOutputDir,
+      @Named("randomStringProvider") Provider<String> randomStringProvider,
+      BenchmarkUploader.Factory benchmarkUploderFactory) {
+    this.compiler = compiler;
+    this.moduleTemplate = moduleTemplate;
+    this.benchmarkData = benchmarkData;
+    this.compilerOutputDir = compilerOutputDir;
+    this.randomStringProvider = randomStringProvider;
+    this.benchmarkUploderFactory = benchmarkUploderFactory;
+  }
+
+  @Override
+  public WorkResult call() {
+    // create working dir
+    String randomDirName = randomStringProvider.get();
+    File outputDir = new File(compilerOutputDir, randomDirName);
+    if (!outputDir.mkdirs()) {
+      return new WorkResult(State.FAILED_TO_CREATE_DIR);
+    }
+
+    try {
+      compiler.compile(benchmarkData.getModuleName(), outputDir, benchmarkData.getDevJar(),
+          benchmarkData.getUserJar(), benchmarkData.getCompilerArgs());
+    } catch (CliException e) {
+      cleanupDirectory(outputDir);
+      return new WorkResult(State.FAILED_COMPILE, e.getMessage());
+    }
+
+    try {
+      writeHostPage(outputDir, benchmarkData.getModuleName());
+    } catch (IOException e) {
+      cleanupDirectory(outputDir);
+      return new WorkResult(State.FAILED_TO_GENERATE_HOST_PAGE, e.getMessage());
+    }
+
+    File zipFile = null;
+    try {
+      zipFile = ZipUtil.zipFolder(outputDir, randomStringProvider.get());
+    } catch (IOException e) {
+      return new WorkResult(State.FAILED_TO_ZIP_BENCHMARK, e.getMessage());
+    } finally {
+      cleanupDirectory(outputDir);
+    }
+
+    BenchmarkUploader benchmarkUploader =
+        benchmarkUploderFactory.create(zipFile, benchmarkData.getRunners());
+
+    try {
+      Job job = benchmarkUploader.run(false);
+      return new WorkResult(job);
+    } catch (IOException e) {
+      return new WorkResult(
+          State.FAILED_TO_RUN_ON_RUNNER, "Failed to get results for benchmark: " + e.getMessage());
+    } catch (InterruptedException e) {
+      return new WorkResult(
+          State.FAILED_TO_RUN_ON_RUNNER, "Failed to get results for benchmark: " + e.getMessage());
+    } finally {
+      FileUtils.deleteQuietly(zipFile);
+    }
+  }
+
+  @VisibleForTesting
+  void writeHostPage(File outputDir, String moduleName) throws IOException {
+    String tpl =
+        moduleTemplate.replace("{module_nocache}", moduleName + "/" + moduleName + ".nocache.js");
+    FileOutputStream stream = null;
+    try {
+      stream = new FileOutputStream(new File(outputDir, "index.html"));
+      IOUtils.write(tpl.getBytes("UTF-8"), stream);
+    } finally {
+      IOUtils.closeQuietly(stream);
+    }
+  }
+
+  @VisibleForTesting
+  void cleanupDirectory(File outputDir) {
+    FileUtils.deleteQuietly(outputDir);
+  }
+}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerConfig.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkWorkerConfig.java
similarity index 74%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerConfig.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkWorkerConfig.java
index c8aa9fc..09cdb2f 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerConfig.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/BenchmarkWorkerConfig.java
@@ -11,7 +11,9 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
+
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -23,20 +25,24 @@
  * runners that the worker should launch.
  */
 public class BenchmarkWorkerConfig {
-  public static BenchmarkWorkerConfig from(BenchmarkRun run, File devJar, File userJar) {
-    return new BenchmarkWorkerConfig(run.getModuleName(), run.getRunConfigs(), devJar, userJar);
+  public static BenchmarkWorkerConfig from(
+      BenchmarkRun run, File devJar, File userJar, String compilerArgs) {
+    return new BenchmarkWorkerConfig(
+        run.getModuleName(), run.getRunConfigs(), devJar, userJar, compilerArgs);
   }
 
   private final String moduleName;
   private final List<RunnerConfig> runnerConfigs;
   private final File devJar;
   private final File userJar;
+  private final String compilerArgs;
 
   public BenchmarkWorkerConfig(String moduleName, List<RunnerConfig> runners, File devJar,
-      File userJar) {
+      File userJar, String compilerArgs) {
     this.moduleName = moduleName;
     this.devJar = devJar;
     this.userJar = userJar;
+    this.compilerArgs = compilerArgs;
     this.runnerConfigs = new ArrayList<>(runners);
   }
 
@@ -55,4 +61,8 @@
   public File getUserJar() {
     return userJar;
   }
+
+  public String getCompilerArgs() {
+    return compilerArgs;
+  }
 }
diff --git a/cli/src/main/java/com/google/j2cl/benchmark/cli/Cli.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/Cli.java
new file mode 100644
index 0000000..ff3607d
--- /dev/null
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/Cli.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 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.j2cl.benchmark.cli;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import org.apache.commons.io.IOUtils;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.OptionHandlerFilter;
+import org.kohsuke.args4j.ParserProperties;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * The command line interface for the benchmarking system.
+ */
+public class Cli {
+
+  @Option(name = "-benchmarksLocation", usage = "location of the benchmark repository.")
+  File benchmarksLocation = new File("../benchmarks/");
+
+  @Option(name = "-run_server", usage = "url of the run server")
+  String runServer = "http://localhost:8080";
+
+  @Option(name = "-module_template", usage = "The template html file to use for benchmarks")
+  File moduleTemplate = new File("runner_template.html");
+
+  @Option(name = "-compiler", usage = "which compiler to use, either gwt or j2cl")
+  String compiler = "gwt";
+
+  @Option(name = "-deamon", usage = "Run in deamon mode (Pull changes and benchmark new commits)")
+  boolean deamon;
+
+  @Option(name = "-persistenceDir", usage = "The directory to store information in")
+  File persistenceDir;
+
+  @Option(name = "-gwtSourceLocation", usage = "GWT source code location.")
+  File gwtSourceLocation = new File("../../gwt");
+
+  @Option(name = "-gwtDevJar", usage = "location of a prebuilt gwt dev jar."
+      + " Use together with -skipBuild.")
+  File gwtDevJar;
+  @Option(name = "-gwtUserJar", usage = "location of a prebuilt gwt user jar."
+      + " Use together with -skipBuild.")
+  File gwtUserJar;
+
+  @Option(name = "-threadPoolSize", usage = "How many threads should be used to compile benchmarks")
+  int threadPoolSize = 5;
+
+  @Option(name = "-reportResults", usage = "report results to a spreadsheet")
+  boolean reportResults = false;
+
+  @Option(name = "-reportErrors", usage = "report errors by mail")
+  boolean reportErrors = false;
+
+  @Option(name = "-skipBuild", usage = "skip the build of the SDK")
+  boolean skipBuild = false;
+
+  @Option(name = "-mail_to", usage = "email address to notify if deamon fails")
+  String mailTo;
+
+  @Option(name = "-mail_from", usage = "email address to send from")
+  String mailFrom;
+
+  @Option(name = "-mail_host", usage = "smtp host for sending emails")
+  String mailHost;
+
+  @Option(name = "-mail_username", usage = "email username")
+  String mailUsername;
+
+  @Option(name = "-mail_password", usage = "email password")
+  String mailPassword;
+
+  @Option(name = "-oauth_secret", usage = "oatuh secret file")
+  File oauthSecretFile;
+
+  @Option(name = "-spreadsheetId", usage = "The spreasheet's id that should be used for storing data")
+  String spreadsheetId;
+
+  @Option(name="-compilerArgs", usage="The compiler args to use to compile a benchark")
+  String compilerArgs = "";
+
+  // receives other command line parameters than options
+  @Argument
+  private List<String> benchmarksToRun = Lists.newArrayList();
+
+  String moduleTemplateContent;
+
+  String oauthSecretContent;
+
+  Predicate<String> benchmarkFilter = Predicates.alwaysTrue();
+
+  public static void main(String[] args) throws InterruptedException {
+    new Cli().doMain(args);
+  }
+
+  public void doMain(String[] args) throws InterruptedException {
+    parseOrDie(args);
+    Injector injector = Guice.createInjector(new BenchmarkModule(this));
+    Manager manager = injector.getInstance(Manager.class);
+    manager.execute();
+  }
+
+  private void parseOrDie(String[] args) {
+    ParserProperties parserProperties = ParserProperties.defaults()
+        // Console width of 80
+        .withUsageWidth(80);
+
+    CmdLineParser parser = new CmdLineParser(this, parserProperties);
+
+    try {
+      parser.parseArgument(args);
+
+      try {
+        moduleTemplateContent = loadContentAsString(moduleTemplate);
+      } catch (IOException e) {
+        throw new CmdLineException(parser, "Can not load module template", e);
+      }
+
+      if (deamon) {
+        if (persistenceDir == null) {
+          throw new CmdLineException(parser, "Deamon mode but no persistenDir given", null);
+        }
+        if (!persistenceDir.isDirectory()) {
+          throw new CmdLineException(parser, "persistenceDir needs to be an existing directory", null);
+        }
+      }
+
+      if (reportErrors) {
+        if (mailFrom == null) {
+          throw new CmdLineException(parser, "if -reportErrors is used -mail_from needs to be set",
+              null);
+        }
+
+        if (mailTo == null) {
+          throw new CmdLineException(parser, "if -reportErrors is used -mail_to needs to be set",
+              null);
+        }
+
+        if (mailHost == null) {
+          throw new CmdLineException(parser, "if -reportErrors is used -mail_host needs to be set",
+              null);
+        }
+
+        if (mailUsername == null) {
+          throw new CmdLineException(parser,
+              "if -reportErrors is used -mail_username needs to be set", null);
+        }
+
+        if (mailPassword == null) {
+          throw new CmdLineException(parser,
+              "if -reportErrors is used -mail_password needs to be set", null);
+        }
+      }
+
+      if (reportResults) {
+        if (persistenceDir == null) {
+          throw new CmdLineException(parser, "reportResults mode but no persistenDir given", null);
+        }
+        if (!persistenceDir.isDirectory()) {
+          throw new CmdLineException(parser, "persistenceDir needs to be an existing directory", null);
+        }
+
+        if (spreadsheetId == null) {
+          throw new CmdLineException(parser, "reportResults mode but no spreadsheetId given", null);
+        }
+        if (oauthSecretFile == null) {
+          throw new CmdLineException(parser, "reportResults mode but no oauth_secret given", null);
+        }
+
+        try {
+          oauthSecretContent = loadContentAsString(oauthSecretFile);
+        } catch (IOException e) {
+          throw new CmdLineException(parser, "Can no load content of oauth secret file", e);
+        }
+      }
+
+      if (gwtUserJar == null || gwtDevJar == null) {
+        gwtDevJar = new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-dev.jar");
+        gwtUserJar = new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-user.jar");
+      }
+
+      if (!deamon) {
+        if (!benchmarksToRun.isEmpty()) {
+          final Set<String> normlizedNames = Sets.newHashSet();
+          for (String benchmarkName : benchmarksToRun) {
+            if (!benchmarkName.startsWith("com.google.gwt.benchmark.benchmarks.")) {
+              benchmarkName = "com.google.gwt.benchmark.benchmarks." + benchmarkName;
+            }
+            normlizedNames.add(benchmarkName);
+          }
+
+          benchmarkFilter = new Predicate<String>() {
+
+            @Override
+            public boolean apply(@Nullable String input) {
+              return normlizedNames.contains(input);
+            }
+          };
+
+        }
+
+        // setting values since we do not need these params
+        if (persistenceDir == null) {
+          persistenceDir = new File(".");
+        }
+
+        if (spreadsheetId == null) {
+          spreadsheetId = "no spreadsheet id";
+        }
+
+        if (oauthSecretContent == null) {
+          oauthSecretContent = "no oauth secret";
+        }
+      }
+
+    } catch (CmdLineException e) {
+      System.err.println(e.getMessage());
+      System.err.println("run_system.sh [options...] arguments...");
+      // print the list of available options
+      parser.printUsage(System.err);
+      System.err.println();
+
+      // print option sample. This is useful some time
+      System.err.println("  Example: run_system.sh " + parser.printExample(OptionHandlerFilter.ALL));
+      System.exit(-1);
+    }
+  }
+
+  private static String loadContentAsString(File fileName) throws IOException {
+    try (FileInputStream inputStream = new FileInputStream(fileName)) {
+      return IOUtils.toString(inputStream, "UTF-8");
+    }
+  }
+}
diff --git a/common/src/main/java/com/google/gwt/benchmark/common/shared/service/ServiceException.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/CliException.java
similarity index 66%
copy from common/src/main/java/com/google/gwt/benchmark/common/shared/service/ServiceException.java
copy to cli/src/main/java/com/google/j2cl/benchmark/cli/CliException.java
index 20e9807..b59caf2 100644
--- a/common/src/main/java/com/google/gwt/benchmark/common/shared/service/ServiceException.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/CliException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Google Inc.
+ * Copyright 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
@@ -11,17 +11,18 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.common.shared.service;
+package com.google.j2cl.benchmark.cli;
 
 /**
- * Generic base exception for all services.
+ * Thrown by {@link CliInteractor} to signal errors.
  */
-public class ServiceException extends Exception {
+public class CliException extends Exception {
 
-  public ServiceException() {
+  public CliException(String message) {
+    super(message);
   }
 
-  public ServiceException(String message) {
-    super(message);
+  public CliException(String message, Throwable cause) {
+    super(message, cause);
   }
 }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractor.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/CliInteractor.java
similarity index 81%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractor.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/CliInteractor.java
index 72bfe7d..a3f0fd8 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractor.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/CliInteractor.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
 
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -55,21 +55,21 @@
     this.benchmarkSourceLocation = benchmarkSourceLocation;
   }
 
-  public void buildSDK() throws BenchmarkManagerException {
+  public void buildSDK() throws CliException {
     File pullChangesScript = new File(scriptDirectory, "buildSDK");
     runCommand(pullChangesScript.getAbsolutePath() + " " + gwtSourceLocation.getAbsolutePath());
   }
 
-  public void checkout(String commitId) throws BenchmarkManagerException {
+  public void checkout(String commitId) throws CliException {
     File pullChangesScript = new File(scriptDirectory, "checkout");
     runCommand(pullChangesScript.getAbsolutePath() + " " + commitId + " "
         + gwtSourceLocation.getAbsolutePath());
   }
 
   @Override
-  public void compile(String moduleName, File compilerOutputDir, File devJar, File userJar)
-      throws BenchmarkCompilerException {
-    logger.info("compiling: " + moduleName);
+  public void compile(String moduleName, File compilerOutputDir, File devJar, File userJar,
+      String extraArgs) throws CliException {
+    logger.info("compiling: " + moduleName + " with args: " + extraArgs);
     File compileScript = new File(scriptDirectory, "compileModule");
 
     String bsl = benchmarkSourceLocation.getAbsolutePath();
@@ -80,19 +80,19 @@
     String outputDir = compilerOutputDir.getAbsolutePath();
     try {
       runCommand(compileScript.getAbsolutePath() + " " + moduleName + " " + devJar.getAbsolutePath()
-          + " " + userJar.getAbsolutePath() + " " + bsl + " " + outputDir, false);
-    } catch (BenchmarkManagerException e) {
-      throw new BenchmarkCompilerException("failed compile", e);
+          + " " + userJar.getAbsolutePath() + " " + bsl + " " + outputDir + " " + extraArgs, false);
+    } catch (CliException e) {
+      throw new CliException("failed compile", e);
     }
   }
 
-  public String getCurrentCommitId() throws BenchmarkManagerException {
+  public String getCurrentCommitId() throws CliException {
     File gitCommitScript = new File(scriptDirectory, "commitId");
     return runCommand(
         gitCommitScript.getAbsolutePath() + " " + gwtSourceLocation.getAbsolutePath());
   }
 
-  public long getDateForCommitInMsEpoch(String currentCommitId) throws BenchmarkManagerException {
+  public long getDateForCommitInMsEpoch(String currentCommitId) throws CliException {
     File commitDateScript = new File(scriptDirectory, "commitDate");
     String dateForCommitString = runCommand(commitDateScript.getAbsolutePath() + " "
         + gwtSourceLocation.getAbsolutePath() + " " + currentCommitId);
@@ -101,7 +101,7 @@
     return Long.valueOf(dateForCommitString) * 1000;
   }
 
-  public String getLastCommitId() throws BenchmarkManagerException {
+  public String getLastCommitId() throws CliException {
     Properties prop = new Properties();
     FileInputStream stream = null;
     try {
@@ -110,24 +110,24 @@
       String commitId = prop.getProperty("commitId");
       if (commitId == null) {
         logger.severe("can not load last commitId from store");
-        throw new BenchmarkManagerException("can not load last commitId from store");
+        throw new CliException("can not load last commitId from store");
       }
       return commitId;
     } catch (IOException e) {
       logger.log(Level.WARNING, "Can not read commit from store file", e);
-      throw new BenchmarkManagerException("Can not read commit from store file", e);
+      throw new CliException("Can not read commit from store file", e);
     } finally {
       IOUtils.closeQuietly(stream);
     }
   }
 
-  public void maybeCheckoutNextCommit(String baseCommitId) throws BenchmarkManagerException {
+  public void maybeCheckoutNextCommit(String baseCommitId) throws CliException {
     File pullChangesScript = new File(scriptDirectory, "maybe_checkout_next_commit");
     runCommand(pullChangesScript.getAbsolutePath() + " " + baseCommitId + " "
         + gwtSourceLocation.getAbsolutePath());
   }
 
-  public void storeCommitId(String commitId) throws BenchmarkManagerException {
+  public void storeCommitId(String commitId) throws CliException {
 
     Properties prop = new Properties();
     prop.setProperty("commitId", commitId);
@@ -139,18 +139,18 @@
 
     } catch (IOException e) {
       logger.log(Level.WARNING, "Can not read commit from store file", e);
-      throw new BenchmarkManagerException("Can not read commit from store file", e);
+      throw new CliException("Can not read commit from store file", e);
     } finally {
       IOUtils.closeQuietly(stream);
     }
   }
 
-  private String runCommand(String command) throws BenchmarkManagerException {
+  private String runCommand(String command) throws CliException {
     return runCommand(command, true);
   }
 
   private String runCommand(String command, boolean useErrorSteam)
-      throws BenchmarkManagerException {
+      throws CliException {
     InputStream stream = null;
     try {
       Process process = Runtime.getRuntime().exec(command);
@@ -161,7 +161,7 @@
         String error =
             "Command returned with " + exitValue + " " + IOUtils.toString(stream, "UTF-8");
         logger.warning(error);
-        throw new BenchmarkManagerException(error);
+        throw new CliException(error);
       }
 
       stream = process.getInputStream();
@@ -169,7 +169,7 @@
 
     } catch (IOException | InterruptedException e) {
       logger.log(Level.WARNING, "Can not run command", e);
-      throw new BenchmarkManagerException("Can not run command");
+      throw new CliException("Can not run command");
     } finally {
       IOUtils.closeQuietly(stream);
     }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkFinder.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/GwtBenchmarkFinder.java
similarity index 90%
copy from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkFinder.java
copy to cli/src/main/java/com/google/j2cl/benchmark/cli/GwtBenchmarkFinder.java
index bc68f0c..4633021 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkFinder.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/GwtBenchmarkFinder.java
@@ -11,9 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
-
-import com.google.inject.name.Named;
+package com.google.j2cl.benchmark.cli;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -21,6 +19,7 @@
 import java.util.List;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * BenchmarkFinder will traverse a directory structure and find all benchmarks in it.
@@ -28,12 +27,12 @@
  * Currently benchmarks need to be located under com.google.gwt.benchmark.benchmarks in order to get
  * picked up by the collector and have the word 'Benchmark' in them, e.g. 'MyCoolBenchmark.gwt.xml'.
  */
-public class BenchmarkFinder {
+public class GwtBenchmarkFinder {
 
   private File benchmarkSourceLocation;
 
   @Inject
-  public BenchmarkFinder(@Named("benchmarkSourceLocation") File benchmarkSourceLocation) {
+  public GwtBenchmarkFinder(@Named("benchmarkSourceLocation") File benchmarkSourceLocation) {
     this.benchmarkSourceLocation = benchmarkSourceLocation;
   }
 
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/MailReporter.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/MailReporter.java
similarity index 95%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/MailReporter.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/MailReporter.java
index 87ad80c..fbb8d58 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/MailReporter.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/MailReporter.java
@@ -11,9 +11,8 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
 
-import com.google.gwt.benchmark.compileserver.server.runners.settings.MailSettings;
 import com.google.inject.Inject;
 
 import java.util.Properties;
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/MailSettings.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/MailSettings.java
similarity index 94%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/MailSettings.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/MailSettings.java
index df4e3e2..70ea8a4 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/MailSettings.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/MailSettings.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.runners.settings;
+package com.google.j2cl.benchmark.cli;
 
 /**
  * Settings relevant for sending an email.
diff --git a/cli/src/main/java/com/google/j2cl/benchmark/cli/Manager.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/Manager.java
new file mode 100644
index 0000000..63e234b
--- /dev/null
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/Manager.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright 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.j2cl.benchmark.cli;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.inject.Provider;
+import com.google.inject.name.Named;
+import com.google.j2cl.benchmark.cli.BenchmarkRun.Result;
+import com.google.j2cl.benchmark.cli.BenchmarkRun.Result.State;
+import com.google.j2cl.benchmark.cli.BenchmarkWorker.WorkResult;
+import com.google.j2cl.benchmark.common.runner.JobResult;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigs;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+/**
+ * Manager compiles and runs a series of benchmarks.
+ */
+public class Manager {
+
+  private static final Logger logger = Logger.getLogger(Manager.class.getName());
+
+  private static final long TICK_INTERVAL = 10 * 1000L;
+
+  private static final String NEWLINE = "\n";
+
+  private static List<String> getNonSuccessfulRuns(List<BenchmarkRun> results) {
+    List<String> list = new ArrayList<>();
+    for (BenchmarkRun benchmarkRun : results) {
+      if (benchmarkRun.isFailed()) {
+        for (Entry<RunnerConfig, Result> result : benchmarkRun.getResults().entrySet()) {
+          if (result.getValue().getState() == BenchmarkRun.Result.State.FAILED_RUN) {
+            list.add(benchmarkRun.getModuleName() + " " + result.getKey() + " "
+                + benchmarkRun.getReportingName());
+          }
+        }
+      } else {
+        for (Entry<RunnerConfig, Result> result : benchmarkRun.getResults().entrySet()) {
+          if (result.getValue().getState() != Result.State.SUCCESSFUL_RUN) {
+            list.add(benchmarkRun.getModuleName() + " " + result.getKey() + " "
+                + benchmarkRun.getReportingName());
+          }
+        }
+      }
+    }
+    return list;
+  }
+
+  private final BenchmarkFinder benchmarkFinder;
+
+  private final BenchmarkWorker.Factory benchmarkWorkerFactory;
+
+  private long currentCommitDateMsEpoch;
+
+  private String currentCommitId;
+
+  private final MailReporter errorReporter;
+
+  private String lastSuccessfulCommitId;
+
+  private ExecutorService pool;
+
+  private Provider<ExecutorService> poolProvider;
+
+  private final BenchmarkReporter.Factory reporterFactory;
+
+  protected final CliInteractor cliInteractor;
+
+  private boolean reportResults;
+
+  protected final File devJar;
+
+  protected final File userJar;
+
+  private final Predicate<String> benchmarkFilter;
+
+  private final boolean deamonMode;
+
+  private final boolean skipSDKBuild;
+
+  private final String compilerArgs;
+
+  @Inject
+  public Manager(BenchmarkFinder collector, BenchmarkWorker.Factory benchmarkWorkerFactory,
+      @Named("managerPoolSize") Provider<ExecutorService> poolProvider,
+      BenchmarkReporter.Factory reporterFactory, @Named("reportResults") boolean reportResults,
+      CliInteractor commitReader, MailReporter errorReporter,
+      @Named("benchmarkFilter") Predicate<String> benchmarkFilter,
+      @Named("deamonMode") boolean deamonMode, @Named("skipSDKBuild") boolean skipSDKBuild,
+      @Named("gwtDevJar") File devJar, @Named("gwtUserJar") File userJar,
+      @Named("compilerArgs") String compilerArgs) {
+    this.benchmarkFinder = collector;
+    this.benchmarkWorkerFactory = benchmarkWorkerFactory;
+    this.poolProvider = poolProvider;
+    this.reporterFactory = reporterFactory;
+    this.reportResults = reportResults;
+    this.cliInteractor = commitReader;
+    this.errorReporter = errorReporter;
+    this.benchmarkFilter = benchmarkFilter;
+    this.deamonMode = deamonMode;
+    this.skipSDKBuild = skipSDKBuild;
+    this.devJar = devJar;
+    this.userJar = userJar;
+    this.compilerArgs = compilerArgs;
+  }
+
+  private BenchmarkRun createBenchmarkRunForModule(
+      String moduleName, String commitId, long currentCommitDateMsEpoch, String reportingName) {
+    BenchmarkRun br =
+        new BenchmarkRun(moduleName, commitId, currentCommitDateMsEpoch, reportingName);
+    for (RunnerConfig config : RunnerConfigs.getAllRunners()) {
+      br.addRunner(config);
+    }
+    return br;
+  }
+
+  protected void reportNonSuccesulRuns(List<BenchmarkRun> results) {
+    Collection<String> runs = getNonSuccessfulRuns(results);
+    if (!runs.isEmpty()) {
+      StringBuilder builder = new StringBuilder();
+      builder.append("Benchmarks failed executing - stopping system\n\n");
+      builder.append("Failed Benchmarks: \n");
+      for (String errorModule : runs) {
+        builder.append(String.format("%s \n", errorModule));
+      }
+      logger.severe(
+          String.format("Benchmarks failed executing - stopping system\n%s", builder.toString()));
+      reportError(builder.toString());
+    }
+  }
+
+  protected boolean maybeReportResults(String commitId, List<BenchmarkRun> results) {
+    if (!reportResults) {
+      return true;
+    }
+
+    BenchmarkReporter benchmarkReporter = reporterFactory.create(results, commitId);
+
+    if (!benchmarkReporter.report()) {
+      reportError("Reporter failed to report results, shutting down the system");
+      return false;
+    }
+
+    return true;
+  }
+
+  protected boolean checkoutLastCommit() {
+    try {
+      logger.info("Getting last commit");
+      setLastCommit(cliInteractor.getLastCommitId());
+      currentCommitId = lastSuccessfulCommitId;
+      logger.info(String.format("Last commit was %s", currentCommitId));
+      currentCommitDateMsEpoch = cliInteractor.getDateForCommitInMsEpoch(currentCommitId);
+      logger.info("Checking out last commit");
+      cliInteractor.checkout(lastSuccessfulCommitId);
+      logger.info(String.format("found a new commit %s", currentCommitId));
+      logger.info("Getting its commit date");
+      currentCommitDateMsEpoch = cliInteractor.getDateForCommitInMsEpoch(currentCommitId);
+      return true;
+    } catch (CliException e) {
+      logger.log(Level.WARNING, "Can not checkout commit - shutting down", e);
+      reportError("Can not update git repo");
+      return false;
+    }
+  }
+
+  protected boolean hasUpdates;
+
+  protected boolean checkForPossibleUpdates() {
+    hasUpdates = false;
+    try {
+      cliInteractor.maybeCheckoutNextCommit(lastSuccessfulCommitId);
+      String commitId = cliInteractor.getCurrentCommitId();
+      hasUpdates = !currentCommitId.equals(commitId);
+      currentCommitId = commitId;
+      return true;
+
+    } catch (CliException e) {
+      logger.log(Level.WARNING, "Can not update repository", e);
+      reportError("Can not update git repo");
+      return false;
+    }
+  }
+
+
+
+  protected boolean buildSDK() {
+    try {
+      logger.info("Building SDK");
+      cliInteractor.buildSDK();
+      logger.info("Successfully build SDK");
+      return true;
+    } catch (CliException e) {
+      logger.log(Level.WARNING, "Can not build SDK", e);
+      return false;
+    }
+  }
+
+  public void execute() throws InterruptedException {
+    if (deamonMode) {
+      runAsDeamon();
+    } else {
+      runAsSingleBenchmarRun();
+    }
+  }
+
+  protected void runAsSingleBenchmarRun() throws InterruptedException {
+    if (!skipSDKBuild) {
+      if(!buildSDK()) {
+        System.err.println("Can not build SDK");
+        return;
+      }
+    }
+
+    List<RunInfo> jobFutures = startBenchmarking("no commit id", System.currentTimeMillis());
+    waitForJobsAndUpdate(Lists.newArrayList(jobFutures));
+    ImmutableList<BenchmarkRun> finishedRuns = convertToBenchmarkRuns(jobFutures);
+    String output = convertRunToString(finishedRuns);
+    printOutput(output);
+  }
+
+  @VisibleForTesting
+  void printOutput(String output) {
+    System.out.print(output);
+  }
+
+  protected String convertRunToString(List<BenchmarkRun> finishedRuns) {
+    StringBuilder builder = new StringBuilder();
+    builder.append("Results:");
+    builder.append(NEWLINE);
+
+    for (BenchmarkRun benchmarkRun : finishedRuns) {
+      builder.append("  "  + benchmarkRun.getModuleName());
+      builder.append(NEWLINE);
+      if (benchmarkRun.isFailed() && benchmarkRun.getState() != BenchmarkRun.State.FAILED_TO_RUN_ON_RUNNER) {
+        // all of them failed!
+        for (RunnerConfig config : benchmarkRun.getRunConfigs()) {
+          String message = String.format("    %s: Failed (%s)", config, benchmarkRun.getErrorMessage());
+          builder.append(message);
+          builder.append(NEWLINE);
+        }
+      } else {
+        for (RunnerConfig config : benchmarkRun.getRunConfigs()) {
+          Result result = benchmarkRun.getResults().get(config);
+          if (result.getState() == State.SUCCESSFUL_RUN) {
+            String message = String.format("    %s: %f runs/second", config, result.getRunsPerSecond());
+            builder.append(message);
+            builder.append(NEWLINE);
+          } else {
+            String message = String.format("    %s: Failed (%s)", config, result.getErrorMessage());
+            builder.append(message);
+            builder.append(NEWLINE);
+          }
+        }
+      }
+    }
+    return builder.toString();
+  }
+
+  protected void runAsDeamon() throws InterruptedException {
+    // check out last successful commit
+    if (!checkoutLastCommit()) {
+      return;
+    }
+
+    while (true) {
+      if (!checkForPossibleUpdates()) {
+        return;
+      }
+
+      if (!hasUpdates) {
+        sleep(TICK_INTERVAL);
+        continue;
+      }
+
+      // we have a new commit
+      if (!buildSDK()) {
+        reportError("Can not build SDK");
+        return;
+      }
+
+      List<RunInfo> jobFutures = startBenchmarking(currentCommitId, currentCommitDateMsEpoch);
+      waitForJobsAndUpdate(Lists.newArrayList(jobFutures));
+      ImmutableList<BenchmarkRun> finishedRuns = convertToBenchmarkRuns(jobFutures);
+      boolean successfulRun = getNonSuccessfulRuns(finishedRuns).isEmpty();
+
+      if (!successfulRun) {
+        reportNonSuccesulRuns(finishedRuns);
+        return;
+      }
+
+      if (!maybeReportResults(currentCommitId, finishedRuns)) {
+        reportError("Unable to save results to spreadsheet");
+        return;
+      }
+
+      if (!updateCurrentCommit(currentCommitId)) {
+        reportError("Can not store last commitId - quitting");
+        return;
+      }
+    }
+  }
+
+  protected ImmutableList<BenchmarkRun> convertToBenchmarkRuns(List<RunInfo> jobFutures) {
+    ImmutableList<BenchmarkRun> finishedRuns =
+        FluentIterable.from(jobFutures).transform(new Function<RunInfo, BenchmarkRun>() {
+
+            @Override
+          public BenchmarkRun apply(RunInfo input) {
+            return input.benchmarkRun;
+          }
+        }).toList();
+    return finishedRuns;
+  }
+
+  protected boolean updateCurrentCommit(String commit) {
+    try {
+      cliInteractor.storeCommitId(commit);
+      setLastCommit(commit);
+      return true;
+    } catch (CliException e) {
+      logger.log(Level.WARNING, "Can not store last commitId", e);
+      return false;
+    }
+  }
+
+  protected void waitForJobsAndUpdate(List<RunInfo> jobFutures) throws InterruptedException {
+    while (!jobFutures.isEmpty()) {
+      try {
+        sleepWaitingForJobs();
+      } catch (InterruptedException e) {
+        throw e;
+      }
+      Iterables.removeIf(jobFutures, new Predicate<RunInfo>() {
+          @Override
+        public boolean apply(RunInfo info) {
+          if (info.future.isDone()) {
+            WorkResult workResult = null;
+            try {
+              workResult = info.future.get();
+            } catch (InterruptedException | ExecutionException e) {
+              // can not be thrown since work is done
+            }
+
+            if (workResult.isFailed() || !workResult.job.isSucceeded()) {
+              if (workResult.job == null) {
+                // the whole job failed
+                switch(workResult.state) {
+                  case FAILED_COMPILE:
+                    info.benchmarkRun.setFailedCompile(workResult.reason);
+                    break;
+
+                  case FAILED_TO_CREATE_DIR:
+                    info.benchmarkRun.setFailedToCreateDirectory();
+                    break;
+                  case FAILED_TO_GENERATE_HOST_PAGE:
+                    info.benchmarkRun.setFailedHostPageGenerationFailed(workResult.reason);
+                    break;
+                  case FAILED_TO_RUN_ON_RUNNER:
+                    info.benchmarkRun.setFailedToRunOnServer();
+                    break;
+                  case FAILED_TO_ZIP_BENCHMARK:
+                    info.benchmarkRun.setFailedToCreateDirectory();
+                    break;
+                  default:
+                    throw new RuntimeException();
+                }
+              } else {
+                for (JobResult jobResult : workResult.job.getJobResults()) {
+                  if (jobResult.isSucceded()) {
+                    info.benchmarkRun.addResult(jobResult.getConfig(), jobResult.getResult());
+                  } else {
+                    info.benchmarkRun.getResults().get(jobResult.getConfig()).setErrorMessage(
+                        jobResult.getErrorMessage());
+                    info.benchmarkRun.setFailedToRunOnServer();
+                  }
+                }
+              }
+            } else {
+              for (JobResult jobResult : workResult.job.getJobResults()) {
+                info.benchmarkRun.addResult(jobResult.getConfig(), jobResult.getResult());
+              }
+            }
+            return true;
+          }
+          return false;
+        }
+      });
+    }
+  }
+
+
+
+  protected void reportError(String message) {
+    errorReporter.sendEmail(message);
+  }
+
+  private void setLastCommit(String commitId) {
+    lastSuccessfulCommitId = commitId;
+  }
+
+  protected static class RunInfo {
+    public Future<BenchmarkWorker.WorkResult> future;
+    public BenchmarkRun benchmarkRun;
+  }
+
+  protected List<RunInfo> startBenchmarking(String commitId, long currentCommitDateMsEpoch) {
+    logger.info("Starting benchmark runners");
+    pool = poolProvider.get();
+    List<RunInfo> jobFutures = Lists.newArrayList();
+
+    for (String benchmarkModuleName : benchmarkFinder.get()) {
+
+      // we are currently ignoring D8 benchmarks until we have a V8 runner.
+      if (benchmarkModuleName.endsWith("D8")) {
+        continue;
+      }
+
+      // TODO(dankurka): fix navier and remove this
+      if (benchmarkModuleName.endsWith("NavierStokesBenchmarkGWT")) {
+        continue;
+      }
+
+      if (!benchmarkFilter.apply(benchmarkModuleName)) {
+        logger.info(String.format("Removed benchmark due to filter: %s", benchmarkModuleName));
+        continue;
+      }
+
+      if (deamonMode) {
+        // in deamon mode we want to test different configs of the compiler
+        for (CompilerSettings compilerSettings : COMPILER_SETINGS) {
+          String actualArguments = compilerArgs + " " + compilerSettings.args;
+          RunInfo runInfo = submitJob(benchmarkModuleName, commitId, currentCommitDateMsEpoch,
+              actualArguments, compilerSettings.name);
+          jobFutures.add(runInfo);
+        }
+      } else {
+        RunInfo runInfo =
+            submitJob(benchmarkModuleName, commitId, currentCommitDateMsEpoch, compilerArgs, "");
+        jobFutures.add(runInfo);
+      }
+    }
+    return jobFutures;
+  }
+
+  protected RunInfo submitJob(String benchmarkModuleName, String commitId,
+      long currentCommitDateMsEpoch, String compilerArgs, String reportingName) {
+    BenchmarkRun br = createBenchmarkRunForModule(
+        benchmarkModuleName, commitId, currentCommitDateMsEpoch, reportingName);
+    BenchmarkWorker worker = benchmarkWorkerFactory.create(
+        BenchmarkWorkerConfig.from(br, devJar, userJar, compilerArgs));
+    RunInfo runInfo = new RunInfo();
+    runInfo.benchmarkRun = br;
+    runInfo.future = pool.submit(worker);
+    return runInfo;
+  }
+
+  @VisibleForTesting
+  void sleep(long timeInMs) throws InterruptedException {
+    Thread.sleep(timeInMs);
+  }
+
+  @VisibleForTesting
+  void sleepWaitingForJobs() throws InterruptedException {
+    Thread.sleep(5 * 1000l);
+  }
+
+  private static final ImmutableList<CompilerSettings> COMPILER_SETINGS = ImmutableList.of(
+      // TODO(dankurka): Talk with Goktug about using precondistions here
+      new CompilerSettings("Normal", ""),
+      new CompilerSettings("FullOptimized", "-optimize 9 -XnoclassMetadata " +
+          "-XnocheckCasts"),
+      new CompilerSettings("NoneOptimized", "-style PRETTY -optimize 0"));
+
+  private static class CompilerSettings {
+    public final String args;
+    public String name;
+    public CompilerSettings(String name, String args) {
+      this.name = name;
+      this.args = args;
+    }
+  }
+
+}
diff --git a/common/src/main/java/com/google/gwt/benchmark/oauth2/server/OAuthHelper.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/OAuthHelper.java
similarity index 97%
rename from common/src/main/java/com/google/gwt/benchmark/oauth2/server/OAuthHelper.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/OAuthHelper.java
index 3d8070f..1db13ae 100644
--- a/common/src/main/java/com/google/gwt/benchmark/oauth2/server/OAuthHelper.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/OAuthHelper.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.oauth2.server;
+package com.google.j2cl.benchmark.cli;
 
 import com.google.api.client.auth.oauth2.Credential;
 import com.google.api.client.extensions.java6.auth.oauth2.AbstractPromptReceiver;
diff --git a/launcher/src/main/java/com/google/gwt/benchmark/launcher/OAuthWriter.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/OAuthWriter.java
similarity index 94%
rename from launcher/src/main/java/com/google/gwt/benchmark/launcher/OAuthWriter.java
rename to cli/src/main/java/com/google/j2cl/benchmark/cli/OAuthWriter.java
index d953424..d78638b 100644
--- a/launcher/src/main/java/com/google/gwt/benchmark/launcher/OAuthWriter.java
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/OAuthWriter.java
@@ -11,11 +11,10 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.launcher;
+package com.google.j2cl.benchmark.cli;
 
 import com.google.api.client.util.Charsets;
 import com.google.common.io.Files;
-import com.google.gwt.benchmark.oauth2.server.OAuthHelper;
 
 import java.io.File;
 import java.io.IOException;
diff --git a/cli/src/main/java/com/google/j2cl/benchmark/cli/RunZip.java b/cli/src/main/java/com/google/j2cl/benchmark/cli/RunZip.java
new file mode 100644
index 0000000..0a532ef
--- /dev/null
+++ b/cli/src/main/java/com/google/j2cl/benchmark/cli/RunZip.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 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.j2cl.benchmark.cli;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.j2cl.benchmark.common.runner.Job;
+import com.google.j2cl.benchmark.common.runner.JobResult;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigs;
+import com.google.j2cl.benchmark.common.util.ZipUtil;
+
+import org.apache.commons.io.FileUtils;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.OptionHandlerFilter;
+import org.kohsuke.args4j.ParserProperties;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * RunZip can upload a zip file containing a benchmark to the benchmark infratructure and display
+ * results.
+ */
+public class RunZip {
+
+  @Option(name="-runnerServerUrl",usage="the url to the runner server")
+  String runnerServerUrl = "http://localhost:8080";
+
+  @Option(name="-benchmark", usage="The zip file that contains your benchmark", required = true)
+  File benchmark;
+
+  @Option(name="-runners", usage="The browsers to run your benchmark against (Default all)")
+  List<String> runners = Lists.newArrayList();
+
+  @Option(name="-zipFolder")
+  boolean zipFolder;
+
+  private List<RunnerConfig> runnerConfigs;
+
+  public static void main(String[] args) {
+    new RunZip().doMain(args);
+  }
+
+  @VisibleForTesting
+  void doMain(String[] args) {
+    if (!parse(args)) {
+      return;
+    }
+
+    File zipFile = null;
+    try {
+      if (zipFolder) {
+        zipFile = ZipUtil.zipFolder(benchmark, UUID.randomUUID().toString());
+      }
+      BenchmarkUploader benchmarkUploader =
+          createBenchmarkUploader(runnerServerUrl, zipFolder? zipFile : benchmark, runnerConfigs);
+      Job job = benchmarkUploader.run(true);
+      System.out.println("Results:");
+      for (JobResult result : job.getJobResults()) {
+        if (result.isSucceded()) {
+          System.out.println(result.getConfig() + ": " + result.getResult());
+        } else {
+          System.out.println(result.getConfig() + ": Failed (" + result.getErrorMessage() + ")");
+        }
+      }
+    } catch (IOException e) {
+      System.err.println("Error running benchmark");
+      e.printStackTrace(System.err);
+    } catch (InterruptedException e) {
+      // probably a CTRL-c
+      System.out.println("quitting");
+    } finally {
+      FileUtils.deleteQuietly(zipFile);
+    }
+  }
+
+  private List<RunnerConfig> parseRunner(CmdLineParser parser, List<String> commandLineRunners)
+      throws CmdLineException {
+    List<RunnerConfig> runnerConfigs = Lists.newArrayList();
+    for (String runner : commandLineRunners) {
+      switch (runner) {
+        case "chrome":
+          runnerConfigs.add(RunnerConfigs.CHROME_LINUX);
+          break;
+        case "firefox":
+          runnerConfigs.add(RunnerConfigs.FIREFOX_LINUX);
+          break;
+        case "ie10":
+          runnerConfigs.add(RunnerConfigs.IE10_WIN);
+          break;
+        case "ie11":
+          runnerConfigs.add(RunnerConfigs.IE11_WIN);
+          break;
+        default:
+          throw new CmdLineException(parser, "Invalid value for -runners: " + runner
+              + ". Allowed values: chrome, firefox, ie10, ie11", null);
+      }
+    }
+    return runnerConfigs;
+  }
+
+  private boolean parse(String[] args) {
+    ParserProperties parserProperties = ParserProperties.defaults()
+    // Console width of 80
+        .withUsageWidth(80);
+
+    CmdLineParser parser = new CmdLineParser(this, parserProperties);
+
+    try {
+      parser.parseArgument(args);
+      runnerConfigs =
+          runners.isEmpty() ? RunnerConfigs.getAllRunners() : parseRunner(parser, runners);
+      return true;
+    } catch (CmdLineException e) {
+      System.err.println(e.getMessage());
+      System.err.println("run_zip.sh [options...] arguments...");
+      // print the list of available options
+      parser.printUsage(System.err);
+      System.err.println();
+      // print option sample. This is useful some time
+      System.err.println("  Example: run_zip.sh " + parser.printExample(OptionHandlerFilter.ALL));
+      return false;
+    }
+  }
+
+  @VisibleForTesting
+  BenchmarkUploader createBenchmarkUploader(String serverUrl, File zip,
+      List<RunnerConfig> runnerConfigs) {
+    return new BenchmarkUploader(serverUrl, zip, runnerConfigs);
+  }
+}
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkFinderTest.java b/cli/src/test/java/com/google/j2cl/benchmark/cli/BenchmarkFinderTest.java
similarity index 94%
rename from compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkFinderTest.java
rename to cli/src/test/java/com/google/j2cl/benchmark/cli/BenchmarkFinderTest.java
index 45d1e2b..7fdfdf7 100644
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkFinderTest.java
+++ b/cli/src/test/java/com/google/j2cl/benchmark/cli/BenchmarkFinderTest.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
 
 
 
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporterTest.java b/cli/src/test/java/com/google/j2cl/benchmark/cli/BenchmarkReporterTest.java
similarity index 74%
rename from compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporterTest.java
rename to cli/src/test/java/com/google/j2cl/benchmark/cli/BenchmarkReporterTest.java
index 846689f..82ed643 100644
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporterTest.java
+++ b/cli/src/test/java/com/google/j2cl/benchmark/cli/BenchmarkReporterTest.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -25,8 +25,8 @@
 import com.google.gdata.data.spreadsheet.WorksheetEntry;
 import com.google.gdata.data.spreadsheet.WorksheetFeed;
 import com.google.gdata.util.ServiceException;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkReporter.ReportProgressHandler;
 import com.google.inject.Provider;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigs;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -36,7 +36,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
 
 import java.io.File;
 import java.io.IOException;
@@ -47,18 +46,15 @@
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
 
 /**
  * Test for {@link BenchmarkReporter}.
  */
 public class BenchmarkReporterTest {
-
   private BenchmarkReporter reporter;
-  private HashMap<String, BenchmarkRun> results;
+  private List<BenchmarkRun> results;
   private String commitId;
-  private ReportProgressHandler reportProgressHandler;
   private SpreadsheetService spreadsheetService;
   private Provider<SpreadsheetService> spreadsheetServiceProvider;
   private File oauthDir;
@@ -73,34 +69,34 @@
   @SuppressWarnings("unchecked")
   @Before
   public void setup() throws MalformedURLException, URISyntaxException {
+    SPREAD_SHEET_URL =
+        new URI("https://spreadsheets.google.com/feeds/worksheets/spreadSheetId/private/values")
+            .toURL();
 
-    SPREAD_SHEET_URL = new URI(
-        "https://spreadsheets.google.com/feeds/worksheets/spreadSheetId/private/values").toURL();
+    HEADER_URL1 = new URI("https://foo/module1_reportName1?min-row=1&max-row=1").toURL();
+    HEADER_URL2 = new URI("https://foo/module2_reportName1?min-row=1&max-row=1").toURL();
 
-    HEADER_URL1 = new URI("https://foo/module1?min-row=1&max-row=1").toURL();
-    HEADER_URL2 = new URI("https://foo/module2?min-row=1&max-row=1").toURL();
-
-    COMMIT_URL1 = new URI("https://foo/module1?min-col=1&max-col=1").toURL();
-    COMMIT_URL2 = new URI("https://foo/module2?min-col=1&max-col=1").toURL();
+    COMMIT_URL1 = new URI("https://foo/module1_reportName1?min-col=1&max-col=1").toURL();
+    COMMIT_URL2 = new URI("https://foo/module2_reportName1?min-col=1&max-col=1").toURL();
 
     commitId = "commitId1";
     int commitDate = 77;
 
-    results = new HashMap<>();
-    BenchmarkRun benchmarkRun = new BenchmarkRun("module1", commitId, commitDate);
+    results = Lists.newArrayList();
+    BenchmarkRun benchmarkRun =
+        new BenchmarkRun("com.google.gwt.benchmark.benchmarks.module1", commitId, commitDate, "reportName1");
     benchmarkRun.addRunner(RunnerConfigs.CHROME_LINUX);
     benchmarkRun.addRunner(RunnerConfigs.FIREFOX_LINUX);
     benchmarkRun.addResult(RunnerConfigs.CHROME_LINUX, 2);
     benchmarkRun.addResult(RunnerConfigs.FIREFOX_LINUX, 3);
-    results.put("module1", benchmarkRun);
-    BenchmarkRun benchmarkRun1 = new BenchmarkRun("module2", commitId, commitDate);
+    results.add(benchmarkRun);
+    BenchmarkRun benchmarkRun1 =
+        new BenchmarkRun("com.google.gwt.benchmark.benchmarks.module2", commitId, commitDate, "reportName1");
     benchmarkRun1.addRunner(RunnerConfigs.CHROME_LINUX);
     benchmarkRun1.addRunner(RunnerConfigs.FIREFOX_LINUX);
     benchmarkRun1.addResult(RunnerConfigs.CHROME_LINUX, 4);
     benchmarkRun1.addResult(RunnerConfigs.FIREFOX_LINUX, 5);
-    results.put("module2", benchmarkRun1);
-
-    reportProgressHandler = Mockito.mock(ReportProgressHandler.class);
+    results.add(benchmarkRun1);
 
     spreadsheetService = mock(SpreadsheetService.class);
 
@@ -110,53 +106,56 @@
     oauthDir = mock(File.class);
 
     credential = mock(Credential.class);
-
   }
 
   @SuppressWarnings("unchecked")
   @Test
-  public void testAddToExistingSpreadSheets() throws IOException, URISyntaxException,
-      ServiceException {
-
-    reporter = new BenchmarkReporter(results, commitId, reportProgressHandler, oauthDir, "secret",
-        spreadsheetServiceProvider, "spreadSheetId") {
-
-        @Override
+  public void testAddToExistingSpreadSheets()
+      throws IOException, URISyntaxException, ServiceException {
+    reporter = new BenchmarkReporter(
+        results, commitId, oauthDir, "secret", spreadsheetServiceProvider, "spreadSheetId") {
+      @Override
       Credential authorize() throws IOException, GeneralSecurityException {
         return credential;
       }
 
-        @Override
+      @Override
       boolean sleep(int seconds) {
         return true;
       }
+
+      @Override
+      void sleepBecauseOfBug() throws InterruptedException {}
     };
 
-    List<CellEntry> headerEntriesList = Lists.<CellEntry> newArrayList(
-        mockCellEntry(1, 1, "commit"), mockCellEntry(1, 2, "linux chrome"),
-        mockCellEntry(1, 3, "linux firefox"));
+    List<CellEntry> headerEntriesList = Lists.<CellEntry>newArrayList(mockCellEntry(1, 1, "commit"),
+        mockCellEntry(1, 2, "linux chrome"), mockCellEntry(1, 3, "linux firefox"));
 
     WorksheetFeed worksheetFeed = mock(WorksheetFeed.class);
-    when(spreadsheetService.getFeed(SPREAD_SHEET_URL, WorksheetFeed.class)).thenReturn(
-        worksheetFeed);
+    when(spreadsheetService.getFeed(SPREAD_SHEET_URL, WorksheetFeed.class))
+        .thenReturn(worksheetFeed);
 
     // mock two worksheets
-    WorksheetEntry module1Worksheet = mockWorkSheetEntry("module1", "https://foo/module1");
-    WorksheetEntry module2Worksheet = mockWorkSheetEntry("module2", "https://foo/module2");
+    WorksheetEntry module1Worksheet =
+        mockWorkSheetEntry("module1_reportName1", "https://foo/module1_reportName1");
+    WorksheetEntry module2Worksheet =
+        mockWorkSheetEntry("module2_reportName1", "https://foo/module2_reportName1");
     when(worksheetFeed.getEntries()).thenReturn(Arrays.asList(module1Worksheet, module2Worksheet));
 
     // mock cell feeds of the two work sheets
     CellFeed cellFeedSheet1 = mock(CellFeed.class);
-    when(cellFeedSheet1.getEntries()).thenReturn(Lists.<CellEntry> newArrayList(),
-        Lists.<CellEntry> newArrayList(), headerEntriesList);
+    when(cellFeedSheet1.getEntries())
+        .thenReturn(
+            Lists.<CellEntry>newArrayList(), Lists.<CellEntry>newArrayList(), headerEntriesList);
     when(spreadsheetService.getFeed(HEADER_URL1, CellFeed.class)).thenReturn(cellFeedSheet1);
     CellFeed cellFeedSheet2 = mock(CellFeed.class);
-    when(cellFeedSheet2.getEntries()).thenReturn(Lists.<CellEntry> newArrayList(),
-        Lists.<CellEntry> newArrayList(), headerEntriesList);
+    when(cellFeedSheet2.getEntries())
+        .thenReturn(
+            Lists.<CellEntry>newArrayList(), Lists.<CellEntry>newArrayList(), headerEntriesList);
     when(spreadsheetService.getFeed(HEADER_URL2, CellFeed.class)).thenReturn(cellFeedSheet2);
 
     // mock queries for commit row
-    List<CellEntry> commitsCol = Lists.<CellEntry> newArrayList(mockCellEntry(1, 1, "commit"));
+    List<CellEntry> commitsCol = Lists.<CellEntry>newArrayList(mockCellEntry(1, 1, "commit"));
     CellFeed commitCol1 = mock(CellFeed.class);
     when(commitCol1.getEntries()).thenReturn(commitsCol);
     when(spreadsheetService.getFeed(COMMIT_URL1, CellFeed.class)).thenReturn(commitCol1);
@@ -166,13 +165,19 @@
 
     // return mock for the work
     CellFeed module1CellFeed = mock(CellFeed.class);
-    when(spreadsheetService.getFeed(new URI("https://foo/module1").toURL(), CellFeed.class))
+    when(
+        spreadsheetService.getFeed(
+            new URI("https://foo/module1_reportName1").toURL(), CellFeed.class))
         .thenReturn(module1CellFeed);
     CellFeed module2CellFeed = mock(CellFeed.class);
-    when(spreadsheetService.getFeed(new URI("https://foo/module2").toURL(), CellFeed.class))
+    when(
+        spreadsheetService.getFeed(
+            new URI("https://foo/module2_reportName1").toURL(), CellFeed.class))
         .thenReturn(module2CellFeed);
 
-    reporter.run();
+    boolean report = reporter.report();
+
+    assertThat(report).isTrue();
 
     verify(spreadsheetService).setOAuth2Credentials(credential);
 
@@ -215,33 +220,37 @@
 
   @Test
   public void createWorkSheets() throws IOException, ServiceException, URISyntaxException {
-    reporter = new BenchmarkReporter(results, commitId, reportProgressHandler, oauthDir, "secret",
-        spreadsheetServiceProvider, "spreadSheetId") {
-
-        @Override
+    reporter = new BenchmarkReporter(
+        results, commitId, oauthDir, "secret", spreadsheetServiceProvider, "spreadSheetId") {
+      @Override
       Credential authorize() throws IOException, GeneralSecurityException {
         return credential;
       }
 
-        @Override
+      @Override
       boolean sleep(int seconds) {
         return true;
       }
+
+      @Override
+      void sleepBecauseOfBug() throws InterruptedException {}
     };
 
     WorksheetFeed worksheetFeed = mock(WorksheetFeed.class);
-    when(spreadsheetService.getFeed(SPREAD_SHEET_URL, WorksheetFeed.class)).thenReturn(
-        worksheetFeed);
+    when(spreadsheetService.getFeed(SPREAD_SHEET_URL, WorksheetFeed.class))
+        .thenReturn(worksheetFeed);
     // no worksheets on server
-    when(worksheetFeed.getEntries()).thenReturn(Lists.<WorksheetEntry> newArrayList());
+    when(worksheetFeed.getEntries()).thenReturn(Lists.<WorksheetEntry>newArrayList());
 
     // setup mocking for adding worksheets
     ArgumentCaptor<WorksheetEntry> worksheetEntryCaptor =
         ArgumentCaptor.forClass(WorksheetEntry.class);
-    WorksheetEntry module1Worksheet = mockWorkSheetEntry("module1", "https://foo/module1");
-    WorksheetEntry module2Worksheet = mockWorkSheetEntry("module2", "https://foo/module2");
-    when(worksheetFeed.insert(worksheetEntryCaptor.capture())).thenReturn(module1Worksheet,
-        module2Worksheet);
+    WorksheetEntry module1Worksheet =
+        mockWorkSheetEntry("module1", "https://foo/module1_reportName1");
+    WorksheetEntry module2Worksheet =
+        mockWorkSheetEntry("module2", "https://foo/module2_reportName1");
+    when(worksheetFeed.insert(worksheetEntryCaptor.capture()))
+        .thenReturn(module1Worksheet, module2Worksheet);
 
     // no headers in worksheets
     CellFeed emptyHeaderFeed = mockEmptyCellFeed();
@@ -255,30 +264,33 @@
     when(feedThatGetsHeadersInsertedModule2.insert(cellEntryCaptor2.capture())).thenReturn(null);
 
     // headers
-    List<CellEntry> headerEntriesList = Lists.<CellEntry> newArrayList(
-        mockCellEntry(1, 1, "commit"), mockCellEntry(1, 2, "linux chrome"),
-        mockCellEntry(1, 3, "linux firefox"));
+    List<CellEntry> headerEntriesList = Lists.<CellEntry>newArrayList(mockCellEntry(1, 1, "commit"),
+        mockCellEntry(1, 2, "linux chrome"), mockCellEntry(1, 3, "linux firefox"));
     CellFeed addedHeaderFeed = mock(CellFeed.class);
     when(addedHeaderFeed.getEntries()).thenReturn(headerEntriesList);
 
     // mock queries for commit row
-    List<CellEntry> commitsCol = Lists.<CellEntry> newArrayList(mockCellEntry(1, 1, "commit"));
+    List<CellEntry> commitsCol = Lists.<CellEntry>newArrayList(mockCellEntry(1, 1, "commit"));
     CellFeed commitCol = mock(CellFeed.class);
     when(commitCol.getEntries()).thenReturn(commitsCol);
     when(spreadsheetService.getFeed(COMMIT_URL1, CellFeed.class)).thenReturn(commitCol);
     when(spreadsheetService.getFeed(COMMIT_URL2, CellFeed.class)).thenReturn(commitCol);
 
-    when(spreadsheetService.getFeed(HEADER_URL1, CellFeed.class)).thenReturn(emptyHeaderFeed,
-        feedThatGetsHeadersInsertedModule1, addedHeaderFeed);
-    when(spreadsheetService.getFeed(HEADER_URL2, CellFeed.class)).thenReturn(emptyHeaderFeed,
-        feedThatGetsHeadersInsertedModule2, addedHeaderFeed);
+    when(spreadsheetService.getFeed(HEADER_URL1, CellFeed.class))
+        .thenReturn(emptyHeaderFeed, feedThatGetsHeadersInsertedModule1, addedHeaderFeed);
+    when(spreadsheetService.getFeed(HEADER_URL2, CellFeed.class))
+        .thenReturn(emptyHeaderFeed, feedThatGetsHeadersInsertedModule2, addedHeaderFeed);
 
     CellFeed module1AddCellFeed = mockEmptyCellFeed();
-    when(spreadsheetService.getFeed(new URI("https://foo/module1").toURL(), CellFeed.class))
+    when(
+        spreadsheetService.getFeed(
+            new URI("https://foo/module1_reportName1").toURL(), CellFeed.class))
         .thenReturn(module1AddCellFeed);
 
     CellFeed module2AddCellFeed = mockEmptyCellFeed();
-    when(spreadsheetService.getFeed(new URI("https://foo/module2").toURL(), CellFeed.class))
+    when(
+        spreadsheetService.getFeed(
+            new URI("https://foo/module2_reportName1").toURL(), CellFeed.class))
         .thenReturn(module2AddCellFeed);
 
     ArgumentCaptor<CellEntry> module1AddCellFeedCaptor = ArgumentCaptor.forClass(CellEntry.class);
@@ -286,7 +298,7 @@
     ArgumentCaptor<CellEntry> module2AddCellFeedCaptor = ArgumentCaptor.forClass(CellEntry.class);
     when(module2AddCellFeed.insert(module2AddCellFeedCaptor.capture())).thenReturn(null);
 
-    reporter.run();
+    reporter.report();
 
     assertHeader(cellEntryCaptor1.getAllValues());
     assertHeader(cellEntryCaptor2.getAllValues());
@@ -318,8 +330,8 @@
     List<WorksheetEntry> addedWorkSheets = worksheetEntryCaptor.getAllValues();
 
     assertThat(addedWorkSheets.size()).isEqualTo(2);
-    assertThat(addedWorkSheets.get(0).getTitle().getPlainText()).isEqualTo("module1");
-    assertThat(addedWorkSheets.get(1).getTitle().getPlainText()).isEqualTo("module2");
+    assertThat(addedWorkSheets.get(0).getTitle().getPlainText()).isEqualTo("module1_reportName1");
+    assertThat(addedWorkSheets.get(1).getTitle().getPlainText()).isEqualTo("module2_reportName1");
 
     verify(spreadsheetService).setOAuth2Credentials(credential);
   }
@@ -339,8 +351,8 @@
     assertThat(headerEntries.get(2).getCell().getInputValue()).isEqualTo("linux firefox");
   }
 
-  private WorksheetEntry mockWorkSheetEntry(String title, String url) throws MalformedURLException,
-      URISyntaxException {
+  private WorksheetEntry mockWorkSheetEntry(String title, String url)
+      throws MalformedURLException, URISyntaxException {
     WorksheetEntry worksheetEntry = mock(WorksheetEntry.class);
     when(worksheetEntry.getTitle()).thenReturn(new PlainTextConstruct(title));
     when(worksheetEntry.getCellFeedUrl()).thenReturn(new URI(url).toURL());
@@ -350,22 +362,22 @@
   @Test
   public void testFailingRetries() {
     final List<Integer> waitingTimes = new ArrayList<>();
-    reporter = new BenchmarkReporter(results, commitId, reportProgressHandler, oauthDir, "secret",
-        spreadsheetServiceProvider, "spreadSheetId") {
-        @Override
+    reporter = new BenchmarkReporter(
+        results, commitId, oauthDir, "secret", spreadsheetServiceProvider, "spreadSheetId") {
+      @Override
       void doPostResult() throws Exception {
         throw new Exception();
       }
 
-        @Override
+      @Override
       boolean sleep(int seconds) {
         waitingTimes.add(seconds);
         return true;
       }
     };
-    reporter.run();
+    boolean report = reporter.report();
+    assertThat(report).isFalse();
     assertThat(waitingTimes).containsExactlyElementsIn(BenchmarkReporter.WAITING_TIME_SECONDS);
-    Mockito.verify(reportProgressHandler).onPermanentFailure();
   }
 
   private CellEntry mockCellEntry(int row, int col, String value) {
@@ -382,7 +394,7 @@
 
   private CellFeed mockEmptyCellFeed() {
     CellFeed cellFeed = mock(CellFeed.class);
-    when(cellFeed.getEntries()).thenReturn(Lists.<CellEntry> newArrayList());
+    when(cellFeed.getEntries()).thenReturn(Lists.<CellEntry>newArrayList());
     return cellFeed;
   }
 }
diff --git a/cli/src/test/java/com/google/j2cl/benchmark/cli/BenchmarkWorkerTest.java b/cli/src/test/java/com/google/j2cl/benchmark/cli/BenchmarkWorkerTest.java
new file mode 100644
index 0000000..36d59da
--- /dev/null
+++ b/cli/src/test/java/com/google/j2cl/benchmark/cli/BenchmarkWorkerTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 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.j2cl.benchmark.cli;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import com.google.inject.Provider;
+import com.google.j2cl.benchmark.cli.BenchmarkWorker.WorkResult;
+import com.google.j2cl.benchmark.common.runner.Job;
+import com.google.j2cl.benchmark.common.runner.JobId;
+import com.google.j2cl.benchmark.common.runner.Runner;
+import com.google.j2cl.benchmark.common.runner.Runner.Factory;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigs;
+import com.google.j2cl.benchmark.common.util.ZipUtil;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test for {@link BenchmarkWorker}.
+ */
+public class BenchmarkWorkerTest {
+
+  private RunnerConfig runnerConfig;
+  private BenchmarkCompiler compiler;
+  private String moduleName;
+  private BenchmarkWorker worker;
+  private File benchmarkCompileOutputDir;
+  private Provider<String> randomStringProvider;
+  private String moduleTemplate;
+  private BenchmarkWorkerConfig benchmarkData;
+  private File devJar;
+  private File userJar;
+  private BenchmarkUploader.Factory benchmarkUploderFactory;
+  private File zipFile;
+
+  @Before
+  public void setup() {
+    moduleName = "moduleName1";
+
+    benchmarkCompileOutputDir = new File("./target/test/TestBenchmarkWorker/");
+    if (!benchmarkCompileOutputDir.exists()) {
+      if (!benchmarkCompileOutputDir.mkdirs()) {
+        Assert.fail("failed to create dirs");
+      }
+    }
+
+    runnerConfig = RunnerConfigs.CHROME_LINUX;
+
+    compiler = Mockito.mock(BenchmarkCompiler.class);
+
+    moduleTemplate = "{module_nocache}";
+
+    devJar = mock(File.class);
+    userJar = mock(File.class);
+
+    benchmarkData =
+        new BenchmarkWorkerConfig(moduleName, Arrays.asList(runnerConfig), devJar, userJar, "");
+
+    randomStringProvider = cast(Mockito.mock(Provider.class));
+    benchmarkUploderFactory = mock(BenchmarkUploader.Factory.class);
+
+    worker = new BenchmarkWorker(compiler, moduleTemplate, benchmarkData, benchmarkCompileOutputDir,
+        randomStringProvider, benchmarkUploderFactory) {
+        @Override
+      void writeHostPage(File outputDir, String moduleName) throws IOException {
+        super.writeHostPage(outputDir, moduleName);
+
+        // lets write some files so we can assert that we zip up everything
+        File fooJs = new File(outputDir, "foo.js");
+        FileUtils.writeStringToFile(fooJs, "// foo.js");
+
+        File barDir = new File(outputDir, "bar");
+        barDir.mkdirs();
+
+        File bazJs = new File(barDir, "baz.js");
+        FileUtils.writeStringToFile(bazJs, "// baz.js");
+      }
+    };
+  }
+
+  @After
+  public void tearDown() throws IOException {
+    FileUtils.deleteDirectory(benchmarkCompileOutputDir);
+  }
+
+  @Test
+  public void testCompilerError() throws Exception {
+
+    BenchmarkCompiler compiler = Mockito.mock(BenchmarkCompiler.class);
+    Factory runnerProvider = Mockito.mock(Runner.Factory.class);
+
+    when(randomStringProvider.get()).thenReturn("randomDir1", "randomDirFile1");
+
+    String moduleTemplate = "mytemplate [{module_nocache}]";
+    String moduleName = "moduleName1";
+    BenchmarkWorkerConfig benchmarkData = new BenchmarkWorkerConfig(moduleName,
+        Arrays.asList(RunnerConfigs.CHROME_LINUX), devJar, userJar, "");
+
+    File benchmarkCompileOutputDir = new File("./target/test/TestBenchmarkWorker");
+    File workDir = new File(benchmarkCompileOutputDir, "randomDir1");
+
+    BenchmarkWorker worker = new BenchmarkWorker(compiler, moduleTemplate, benchmarkData,
+        benchmarkCompileOutputDir, randomStringProvider, benchmarkUploderFactory);
+
+    Mockito.doThrow(new CliException("test")).when(compiler).compile(moduleName, workDir, devJar,
+        userJar, "");
+
+    WorkResult workResult = worker.call();
+
+    assertThat(workResult.state).isEqualTo(BenchmarkRun.State.FAILED_COMPILE);
+    Mockito.verify(compiler).compile(Mockito.eq(moduleName), Mockito.<File> anyObject(),
+        Mockito.eq(devJar), Mockito.eq(userJar), Mockito.eq(""));
+    Mockito.verifyZeroInteractions(runnerProvider);
+    assertThat(workDir.exists()).isFalse();
+  }
+
+  @Test
+  public void testBenchmarkWorker() throws Exception {
+    when(randomStringProvider.get()).thenReturn("randomDir1");
+    ArgumentCaptor<File> captor = ArgumentCaptor.forClass(File.class);
+    final BenchmarkUploader benchmarkUploader = mock(BenchmarkUploader.class);
+
+    when(benchmarkUploderFactory.create(captor.capture(), Mockito.<List<RunnerConfig>>anyObject()))
+        .thenAnswer(new Answer<BenchmarkUploader>() {
+          @Override
+          public BenchmarkUploader answer(InvocationOnMock invocation) throws Throwable {
+            zipFile = (File) invocation.getArguments()[0];
+
+            // assert content of zip
+            File tempDir = Files.createTempDir();
+            ZipUtil.unzip(zipFile, tempDir);
+
+            File indexHtml = new File(tempDir, "index.html");
+            assertThat(indexHtml.exists()).isTrue();
+            assertThat(IOUtils.toString(new FileInputStream(indexHtml), "UTF-8")).isEqualTo(
+                "moduleName1/moduleName1.nocache.js");
+
+            File fooJs = new File(tempDir, "foo.js");
+            assertThat(fooJs.exists()).isTrue();
+            assertThat(IOUtils.toString(new FileInputStream(fooJs), "UTF-8")).isEqualTo(
+                "// foo.js");
+
+            File barDir = new File(tempDir, "bar");
+            assertThat(barDir.exists()).isTrue();
+
+            File bazJs = new File(barDir, "baz.js");
+            assertThat(bazJs.exists()).isTrue();
+            assertThat(IOUtils.toString(new FileInputStream(bazJs), "UTF-8")).isEqualTo(
+                "// baz.js");
+            return benchmarkUploader;
+          }
+        });
+
+    Job job = new Job(new JobId("jobId1"), Lists.newArrayList(RunnerConfigs.CHROME_LINUX), 1);
+    when(benchmarkUploader.run(false)).thenReturn(job);
+    WorkResult workResult = worker.call();
+    assertThat(zipFile.exists()).isFalse();
+    assertThat(workResult.job).isSameAs(job);
+    File workDir = new File(benchmarkCompileOutputDir, "randomDir1");
+    Mockito.verify(compiler).compile(moduleName, workDir, devJar, userJar, "");
+    Assert.assertFalse(workDir.exists());
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <T> T cast(Object a) {
+    return (T) a;
+  }
+}
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractorTest.java b/cli/src/test/java/com/google/j2cl/benchmark/cli/CliInteractorTest.java
similarity index 79%
rename from compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractorTest.java
rename to cli/src/test/java/com/google/j2cl/benchmark/cli/CliInteractorTest.java
index 0c40a61..a0cd39b 100644
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractorTest.java
+++ b/cli/src/test/java/com/google/j2cl/benchmark/cli/CliInteractorTest.java
@@ -11,7 +11,12 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.j2cl.benchmark.cli.CliException;
+import com.google.j2cl.benchmark.cli.CliInteractor;
 
 import org.apache.commons.io.IOUtils;
 import org.junit.Assert;
@@ -68,34 +73,26 @@
   }
 
   @Test
-  public void testCompileModule() throws BenchmarkCompilerException, IOException {
-    scriptInteractor.compile("myModule1", compilerOutputDir, devJar, userJar);
+  public void testCompileModule() throws CliException, IOException {
+    scriptInteractor.compile("myModule1", compilerOutputDir, devJar, userJar, "-style PRETTY -foo");
 
-    FileInputStream inputStream = null;
-    try {
-      inputStream = new FileInputStream(new File("./target/test-out"));
+    try (FileInputStream inputStream = new FileInputStream(new File("./target/test-out"));) {
+
       String out = IOUtils.toString(inputStream);
       // Cut off new line char
       out = out.substring(0, out.length() - 1);
 
       String[] split = out.split(";");
-      Assert.assertEquals(5, split.length);
+      assertThat(split.length).isEqualTo(6);
+      assertThat(split[0]).isEqualTo("myModule1");
+      assertThat(new File(split[1]).getAbsolutePath()).isEqualTo(devJar.getAbsolutePath());
+      assertThat(new File(split[2]).getAbsolutePath()).isEqualTo(userJar.getAbsolutePath());
+      assertThat(new File(split[3]).getAbsolutePath()).isEqualTo(
+          benchmarkSourceLocation.getAbsolutePath());
 
-      Assert.assertEquals("myModule1", split[0]);
-      Assert.assertEquals(
-          devJar.getAbsolutePath(),
-          new File(split[1]).getAbsolutePath());
-
-      Assert.assertEquals(
-          userJar.getAbsolutePath(),
-          new File(split[2]).getAbsolutePath());
-
-      Assert.assertEquals(benchmarkSourceLocation.getAbsolutePath(),
-          new File(split[3]).getAbsolutePath());
-      Assert.assertEquals(compilerOutputDir.getAbsolutePath(),
-          new File(split[4]).getAbsolutePath());
-    } finally {
-      IOUtils.closeQuietly(inputStream);
+      assertThat(new File(split[4]).getAbsolutePath()).isEqualTo(
+          compilerOutputDir.getAbsolutePath());
+      assertThat(split[5]).isEqualTo("-style PRETTY -foo");
     }
   }
 
@@ -104,16 +101,16 @@
     scriptInteractor = new CliInteractor(scriptDirectoryFail, persistenceDir, gwtSourceLocation,
         benchmarkSourceLocation);
     try {
-      scriptInteractor.compile("myModule1", compilerOutputDir, devJar, userJar);
+      scriptInteractor.compile("myModule1", compilerOutputDir, devJar, userJar, "-style PRETTY");
       Assert.fail("Expected exception did not occur");
-    } catch (BenchmarkCompilerException e) {
+    } catch (CliException e) {
       Assert.assertEquals("Command returned with 1 This is my errormessage!\n",
           e.getCause().getMessage());
     }
   }
 
   @Test
-  public void testGetCurrentCommitId() throws BenchmarkManagerException {
+  public void testGetCurrentCommitId() throws CliException {
     String commitId = scriptInteractor.getCurrentCommitId();
     // Cut off new line char
     commitId = commitId.substring(0, commitId.length() - 1);
@@ -128,14 +125,14 @@
     try {
       scriptInteractor.getCurrentCommitId();
       Assert.fail("Expected exception did not occur");
-    } catch (BenchmarkManagerException e) {
+    } catch (CliException e) {
       Assert.assertEquals("Command returned with 1 commitId: This is my errormessage!\n",
           e.getMessage());
     }
   }
 
   @Test
-  public void testGetDateForCommit() throws BenchmarkManagerException {
+  public void testGetDateForCommit() throws CliException {
     long date = scriptInteractor.getDateForCommitInMsEpoch("asdf1");
     Assert.assertEquals(1234987000L, date);
   }
@@ -147,14 +144,14 @@
     try {
       scriptInteractor.getDateForCommitInMsEpoch("commitId1");
       Assert.fail("Expected exception did not occur");
-    } catch (BenchmarkManagerException e) {
+    } catch (CliException e) {
       Assert.assertEquals("Command returned with 1 commitDate: This is my errormessage!\n",
           e.getMessage());
     }
   }
 
   @Test
-  public void testBuildSDK() throws BenchmarkManagerException, IOException {
+  public void testBuildSDK() throws CliException, IOException {
     scriptInteractor.buildSDK();
     Assert.assertEquals(gwtSourceLocation.getAbsolutePath(),
         new File(getTestOutput()).getAbsolutePath());
@@ -167,14 +164,14 @@
     try {
       scriptInteractor.buildSDK();
       Assert.fail("Expected exception did not occur");
-    } catch (BenchmarkManagerException e) {
+    } catch (CliException e) {
       Assert.assertEquals("Command returned with 1 buildSDK: This is my errormessage!\n",
           e.getMessage());
     }
   }
 
   @Test
-  public void testCheckout() throws BenchmarkManagerException, IOException {
+  public void testCheckout() throws CliException, IOException {
     scriptInteractor.checkout("commit12");
     String[] split = getTestOutput().split(";");
     Assert.assertEquals(2, split.length);
@@ -189,14 +186,14 @@
     try {
       scriptInteractor.buildSDK();
       Assert.fail("Expected exception did not occur");
-    } catch (BenchmarkManagerException e) {
+    } catch (CliException e) {
       Assert.assertEquals("Command returned with 1 buildSDK: This is my errormessage!\n",
           e.getMessage());
     }
   }
 
   @Test
-  public void testCheckoutNextCommit() throws BenchmarkManagerException, IOException {
+  public void testCheckoutNextCommit() throws CliException, IOException {
     scriptInteractor.maybeCheckoutNextCommit("baseCommit1");
 
     String[] split = getTestOutput().split(";");
@@ -213,7 +210,7 @@
     try {
       scriptInteractor.maybeCheckoutNextCommit("doesntmatter");
       Assert.fail("Expected exception did not occur");
-    } catch (BenchmarkManagerException e) {
+    } catch (CliException e) {
       Assert.assertEquals("Command returned with 1 maybe: This is my errormessage!\n",
           e.getMessage());
     }
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/MailReporterTest.java b/cli/src/test/java/com/google/j2cl/benchmark/cli/MailReporterTest.java
similarity index 90%
rename from compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/MailReporterTest.java
rename to cli/src/test/java/com/google/j2cl/benchmark/cli/MailReporterTest.java
index 342b647..9cf4227 100644
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/MailReporterTest.java
+++ b/cli/src/test/java/com/google/j2cl/benchmark/cli/MailReporterTest.java
@@ -11,11 +11,11 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.cli;
 
-import com.google.gwt.benchmark.compileserver.server.manager.MailReporter.MailHelper;
-import com.google.gwt.benchmark.compileserver.server.manager.MailReporter.PasswordAuthenticator;
-import com.google.gwt.benchmark.compileserver.server.runners.settings.MailSettings;
+
+import com.google.j2cl.benchmark.cli.MailReporter.MailHelper;
+import com.google.j2cl.benchmark.cli.MailReporter.PasswordAuthenticator;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/cli/src/test/java/com/google/j2cl/benchmark/cli/ManagerTest.java b/cli/src/test/java/com/google/j2cl/benchmark/cli/ManagerTest.java
new file mode 100644
index 0000000..24ab838
--- /dev/null
+++ b/cli/src/test/java/com/google/j2cl/benchmark/cli/ManagerTest.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright 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.j2cl.benchmark.cli;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
+import com.google.inject.Provider;
+import com.google.j2cl.benchmark.cli.BenchmarkWorker.WorkResult;
+import com.google.j2cl.benchmark.common.runner.Job;
+import com.google.j2cl.benchmark.common.runner.JobId;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigs;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import javax.annotation.Nullable;
+
+/**
+ * Test for {@link Manager}.
+ */
+public class ManagerTest {
+
+  @SuppressWarnings("unchecked")
+  public static <T> T cast(Object a) {
+    return (T) a;
+  }
+
+  private BenchmarkFinder collector;
+  private BenchmarkWorker.Factory benchmarkWorkerFactory;
+  private Provider<ExecutorService> poolProvider;
+  private BenchmarkReporter.Factory reporterFactory;
+  private Manager manager;
+  private CliInteractor commitReader;
+  private ThreadPoolExecutor threadPoolExecutor;
+  private BenchmarkWorker benchmarkWorker;
+  private BenchmarkReporter benchmarkReporter;
+  private MailReporter errorReporter;
+  private File devJar;
+  private File userJar;
+
+  @Before
+  public void setup() {
+    collector = Mockito.mock(BenchmarkFinder.class);
+    benchmarkWorkerFactory = Mockito.mock(BenchmarkWorker.Factory.class);
+    poolProvider = cast(Mockito.mock(Provider.class));
+    reporterFactory = Mockito.mock(BenchmarkReporter.Factory.class);
+    commitReader = Mockito.mock(CliInteractor.class);
+    threadPoolExecutor = Mockito.mock(ThreadPoolExecutor.class);
+    benchmarkWorker = Mockito.mock(BenchmarkWorker.class);
+    benchmarkReporter = Mockito.mock(BenchmarkReporter.class);
+    errorReporter = Mockito.mock(MailReporter.class);
+    devJar = mock(File.class);
+    userJar = mock(File.class);
+
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testSuccessfulDeamonRun() throws CliException, InterruptedException, ExecutionException {
+
+    final List<String> filterInput = Lists.newArrayList();
+
+    Predicate<String> filterPredicate = new Predicate<String>() {
+        @Override
+      public boolean apply(@Nullable String input) {
+        filterInput.add(input);
+        if (input.equals("ignoredBenchmark")) {
+          return false;
+        }
+        return true;
+      }
+    };
+
+    manager = new Manager(collector, benchmarkWorkerFactory, poolProvider, reporterFactory, true,
+        commitReader, errorReporter, filterPredicate, true, false, devJar, userJar, "") {
+      @Override
+      void sleep(long timeInMs) throws InterruptedException {
+        // we use this to end the test!
+        throw new InterruptedException("endtest");
+      }
+
+      @Override
+      void sleepWaitingForJobs() throws InterruptedException {
+      }
+    };
+
+    when(commitReader.getLastCommitId()).thenReturn("commit1");
+    when(commitReader.getCurrentCommitId()).thenReturn("commit2");
+    when(poolProvider.get()).thenReturn(threadPoolExecutor);
+    when(collector.get()).thenReturn(Arrays.asList("module1", "module2", "ignoredBenchmark"));
+
+    ArgumentCaptor<BenchmarkWorkerConfig> workerConfigCapture =
+        ArgumentCaptor.forClass(BenchmarkWorkerConfig.class);
+
+    Mockito.when(benchmarkWorkerFactory.create(workerConfigCapture.capture())).thenReturn(
+        benchmarkWorker);
+
+    ArgumentCaptor<List<BenchmarkRun>> resultCaptor = cast(ArgumentCaptor.forClass(List.class));
+
+    when(reporterFactory.create(resultCaptor.capture(), eq("commit2"))).thenReturn(
+        benchmarkReporter);
+
+
+    Future<WorkResult> workResultFuture1 = cast(mock(Future.class));
+    Future<WorkResult> workResultFuture2 = cast(mock(Future.class));
+    Future<WorkResult> workResultFuture3 = cast(mock(Future.class));
+    Future<WorkResult> workResultFuture4 = cast(mock(Future.class));
+    Future<WorkResult> workResultFuture5 = cast(mock(Future.class));
+    Future<WorkResult> workResultFuture6 = cast(mock(Future.class));
+
+    when(workResultFuture1.isDone()).thenReturn(false, true);
+    when(workResultFuture2.isDone()).thenReturn(false, true);
+    when(workResultFuture3.isDone()).thenReturn(false, true);
+    when(workResultFuture4.isDone()).thenReturn(false, true);
+    when(workResultFuture5.isDone()).thenReturn(false, true);
+    when(workResultFuture6.isDone()).thenReturn(false, true);
+
+    Job job1 = new Job(new JobId("jobId1"), RunnerConfigs.getAllRunners(), 1);
+    job1.addResult(RunnerConfigs.CHROME_LINUX, 1);
+    job1.addResult(RunnerConfigs.FIREFOX_LINUX, 2);
+    job1.addResult(RunnerConfigs.IE10_WIN, 3);
+    job1.addResult(RunnerConfigs.IE11_WIN, 4);
+    WorkResult workResult1 = new WorkResult(job1);
+    when(workResultFuture1.get()).thenReturn(workResult1);
+
+    Job job2 = new Job(new JobId("jobId2"), RunnerConfigs.getAllRunners(), 2);
+    job2.addResult(RunnerConfigs.CHROME_LINUX, 5);
+    job2.addResult(RunnerConfigs.FIREFOX_LINUX,6);
+    job2.addResult(RunnerConfigs.IE10_WIN, 7);
+    job2.addResult(RunnerConfigs.IE11_WIN, 8);
+    WorkResult workResult2 = new WorkResult(job2);
+    when(workResultFuture2.get()).thenReturn(workResult2);
+
+    Job job3 = new Job(new JobId("jobId3"), RunnerConfigs.getAllRunners(), 3);
+    job3.addResult(RunnerConfigs.CHROME_LINUX, 9);
+    job3.addResult(RunnerConfigs.FIREFOX_LINUX, 10);
+    job3.addResult(RunnerConfigs.IE10_WIN, 11);
+    job3.addResult(RunnerConfigs.IE11_WIN, 12);
+    WorkResult workResult3 = new WorkResult(job3);
+    when(workResultFuture3.get()).thenReturn(workResult3);
+
+    Job job4 = new Job(new JobId("jobId4"), RunnerConfigs.getAllRunners(), 4);
+    job4.addResult(RunnerConfigs.CHROME_LINUX, 13);
+    job4.addResult(RunnerConfigs.FIREFOX_LINUX, 14);
+    job4.addResult(RunnerConfigs.IE10_WIN, 15);
+    job4.addResult(RunnerConfigs.IE11_WIN, 16);
+    WorkResult workResult4 = new WorkResult(job4);
+    when(workResultFuture4.get()).thenReturn(workResult4);
+
+    Job job5 = new Job(new JobId("jobId5"), RunnerConfigs.getAllRunners(), 5);
+    job5.addResult(RunnerConfigs.CHROME_LINUX, 17);
+    job5.addResult(RunnerConfigs.FIREFOX_LINUX, 18);
+    job5.addResult(RunnerConfigs.IE10_WIN, 19);
+    job5.addResult(RunnerConfigs.IE11_WIN, 20);
+    WorkResult workResult5 = new WorkResult(job5);
+    when(workResultFuture5.get()).thenReturn(workResult5);
+
+    Job job6 = new Job(new JobId("jobId6"), RunnerConfigs.getAllRunners(), 6);
+    job6.addResult(RunnerConfigs.CHROME_LINUX, 21);
+    job6.addResult(RunnerConfigs.FIREFOX_LINUX, 22);
+    job6.addResult(RunnerConfigs.IE10_WIN, 23);
+    job6.addResult(RunnerConfigs.IE11_WIN, 24);
+    WorkResult workResult6 = new WorkResult(job6);
+    when(workResultFuture6.get()).thenReturn(workResult6);
+
+    when(threadPoolExecutor.submit(benchmarkWorker))
+        .thenReturn(workResultFuture1, workResultFuture2, workResultFuture3, workResultFuture4,
+            workResultFuture5, workResultFuture6);
+
+    when(benchmarkReporter.report()).thenReturn(true);
+
+    try {
+      manager.execute();
+    } catch (InterruptedException e) {
+      // make sure we throw the thing
+      assertThat(e.getMessage()).isEqualTo("endtest");
+    }
+
+    assertThat(filterInput).containsExactly("module1", "module2", "ignoredBenchmark");
+
+    verify(commitReader).checkout("commit1");
+    verify(commitReader).maybeCheckoutNextCommit("commit1");
+    verify(poolProvider).get();
+    verify(collector).get();
+    verify(threadPoolExecutor, times(6)).submit(benchmarkWorker);
+
+    List<BenchmarkWorkerConfig> workerConfigs = workerConfigCapture.getAllValues();
+    assertThat(workerConfigs.size()).isEqualTo(6);
+
+    assertBenchmarkWorkerConfig(workerConfigs.get(0), "module1");
+    assertBenchmarkWorkerConfig(workerConfigs.get(1), "module1");
+    assertBenchmarkWorkerConfig(workerConfigs.get(2), "module1");
+
+    assertBenchmarkWorkerConfig(workerConfigs.get(3), "module2");
+    assertBenchmarkWorkerConfig(workerConfigs.get(4), "module2");
+    assertBenchmarkWorkerConfig(workerConfigs.get(5), "module2");
+
+    verify(benchmarkReporter).report();
+
+    List<BenchmarkRun> capturedBenchmarkResults = resultCaptor.getValue();
+
+    assertThat(capturedBenchmarkResults.size()).isEqualTo(6);
+
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(0), RunnerConfigs.CHROME_LINUX, 1.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(0), RunnerConfigs.FIREFOX_LINUX, 2.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(0), RunnerConfigs.IE10_WIN, 3.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(0), RunnerConfigs.IE11_WIN, 4.0);
+
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(1), RunnerConfigs.CHROME_LINUX, 5.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(1), RunnerConfigs.FIREFOX_LINUX, 6.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(1), RunnerConfigs.IE10_WIN, 7.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(1), RunnerConfigs.IE11_WIN, 8.0);
+
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(2), RunnerConfigs.CHROME_LINUX, 9.0);
+    assertSuccessfulBenchmarkRun(
+        capturedBenchmarkResults.get(2), RunnerConfigs.FIREFOX_LINUX, 10.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(2), RunnerConfigs.IE10_WIN, 11.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(2), RunnerConfigs.IE11_WIN, 12.0);
+
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(3), RunnerConfigs.CHROME_LINUX, 13.0);
+    assertSuccessfulBenchmarkRun(
+        capturedBenchmarkResults.get(3), RunnerConfigs.FIREFOX_LINUX, 14.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(3), RunnerConfigs.IE10_WIN, 15.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(3), RunnerConfigs.IE11_WIN, 16.0);
+
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(4), RunnerConfigs.CHROME_LINUX, 17.0);
+    assertSuccessfulBenchmarkRun(
+        capturedBenchmarkResults.get(4), RunnerConfigs.FIREFOX_LINUX, 18.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(4), RunnerConfigs.IE10_WIN, 19.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(4), RunnerConfigs.IE11_WIN, 20.0);
+
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(5), RunnerConfigs.CHROME_LINUX, 21.0);
+    assertSuccessfulBenchmarkRun(
+        capturedBenchmarkResults.get(5), RunnerConfigs.FIREFOX_LINUX, 22.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(5), RunnerConfigs.IE10_WIN, 23.0);
+    assertSuccessfulBenchmarkRun(capturedBenchmarkResults.get(5), RunnerConfigs.IE11_WIN, 24.0);
+
+    verifyZeroInteractions(errorReporter);
+  }
+
+  private void assertSuccessfulBenchmarkRun(
+      BenchmarkRun benchmarkRun, RunnerConfig config, double result) {
+    assertThat(benchmarkRun.isFailed()).isFalse();
+    assertThat(
+        benchmarkRun.getResults().get(config).getState())
+        .isEqualTo(BenchmarkRun.Result.State.SUCCESSFUL_RUN);
+    assertThat(benchmarkRun.getResults().get(config)
+        .getRunsPerSecond()).isEqualTo(result);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testFailingDeamonRun() throws CliException, InterruptedException, ExecutionException {
+    final List<String> filterInput = Lists.newArrayList();
+
+    Predicate<String> filterPredicate = new Predicate<String>() {
+        @Override
+      public boolean apply(@Nullable String input) {
+        filterInput.add(input);
+        if (input.equals("ignoredBenchmark")) {
+          return false;
+        }
+        return true;
+      }
+    };
+
+    manager = new Manager(collector, benchmarkWorkerFactory, poolProvider, reporterFactory, true,
+        commitReader, errorReporter, filterPredicate, true, false, devJar, userJar, "") {
+      @Override
+      void sleep(long timeInMs) throws InterruptedException {
+        // we use this to end the test!
+        throw new InterruptedException("endtest");
+      }
+
+      @Override
+      void sleepWaitingForJobs() throws InterruptedException {
+      }
+    };
+
+    when(commitReader.getLastCommitId()).thenReturn("commit1");
+    when(commitReader.getCurrentCommitId()).thenReturn("commit2");
+    when(poolProvider.get()).thenReturn(threadPoolExecutor);
+    when(collector.get()).thenReturn(Arrays.asList("module1", "module2", "ignoredBenchmark"));
+
+    ArgumentCaptor<BenchmarkWorkerConfig> workerConfigCapture =
+        ArgumentCaptor.forClass(BenchmarkWorkerConfig.class);
+
+    Mockito.when(benchmarkWorkerFactory.create(workerConfigCapture.capture())).thenReturn(
+        benchmarkWorker);
+
+    ArgumentCaptor<List<BenchmarkRun>> resultCaptor = cast(ArgumentCaptor.forClass(List.class));
+
+    when(reporterFactory.create(resultCaptor.capture(), eq("commit2"))).thenReturn(
+        benchmarkReporter);
+
+
+    Future<WorkResult> workResultFuture1 = cast(mock(Future.class));
+    Future<WorkResult> workResultFuture2 = cast(mock(Future.class));
+
+    when(workResultFuture1.isDone()).thenReturn(false, true);
+    when(workResultFuture2.isDone()).thenReturn(false, true);
+
+    Job job1 = new Job(new JobId("jobId1"), RunnerConfigs.getAllRunners(), 1);
+    job1.addResult(RunnerConfigs.CHROME_LINUX, 1);
+    job1.addResult(RunnerConfigs.FIREFOX_LINUX, 2);
+    job1.addResult(RunnerConfigs.IE10_WIN, 3);
+    job1.addResult(RunnerConfigs.IE11_WIN, 4);
+    WorkResult workResult1 = new WorkResult(job1);
+    when(workResultFuture1.get()).thenReturn(workResult1);
+    Job job2 = new Job(new JobId("jobId2"), RunnerConfigs.getAllRunners(), 2);
+    job2.setRunFailed(RunnerConfigs.CHROME_LINUX, "just testing");
+    job2.addResult(RunnerConfigs.FIREFOX_LINUX,6);
+    job2.addResult(RunnerConfigs.IE10_WIN, 7);
+    job2.addResult(RunnerConfigs.IE11_WIN, 8);
+    WorkResult workResult2 = new WorkResult(job2);
+    when(workResultFuture2.get()).thenReturn(workResult2);
+
+    when(threadPoolExecutor.submit(benchmarkWorker)).thenReturn(workResultFuture1, workResultFuture2);
+
+    when(benchmarkReporter.report()).thenReturn(true);
+
+    try {
+      manager.execute();
+    } catch (InterruptedException e) {
+      // make sure we throw the thing
+      assertThat(e.getMessage()).isEqualTo("endtest");
+    }
+
+    assertThat(filterInput).containsExactly("module1", "module2", "ignoredBenchmark");
+
+    verify(commitReader).checkout("commit1");
+    verify(commitReader).maybeCheckoutNextCommit("commit1");
+    verify(poolProvider).get();
+    verify(collector).get();
+    verify(threadPoolExecutor, times(6)).submit(benchmarkWorker);
+
+    List<BenchmarkWorkerConfig> workerConfigs = workerConfigCapture.getAllValues();
+    assertThat(workerConfigs.size()).isEqualTo(6);
+
+    assertBenchmarkWorkerConfig(workerConfigs.get(0), "module1");
+    assertBenchmarkWorkerConfig(workerConfigs.get(1), "module1");
+    assertBenchmarkWorkerConfig(workerConfigs.get(2), "module1");
+
+    assertBenchmarkWorkerConfig(workerConfigs.get(3), "module2");
+    assertBenchmarkWorkerConfig(workerConfigs.get(4), "module2");
+    assertBenchmarkWorkerConfig(workerConfigs.get(5), "module2");
+
+    verifyZeroInteractions(benchmarkReporter);
+
+    ArgumentCaptor<String> argCaptorEmail = ArgumentCaptor.forClass(String.class);
+    verify(errorReporter).sendEmail(argCaptorEmail.capture());
+
+    System.out.println(argCaptorEmail.getValue());
+
+    assertThat(argCaptorEmail.getValue()).isEqualTo(
+        "Benchmarks failed executing - stopping system\n" +
+        "\n" +
+        "Failed Benchmarks: \n" +
+        "module1 linux chrome FullOptimized \n" +
+        "module1 linux chrome NoneOptimized \n" +
+        "module2 linux chrome Normal \n" +
+        "module2 linux chrome FullOptimized \n" +
+        "module2 linux chrome NoneOptimized \n" +
+        "");
+  }
+
+  private void assertBenchmarkWorkerConfig(BenchmarkWorkerConfig config, String module) {
+    assertThat(config.getModuleName()).isEqualTo(module);
+    assertThat(config.getDevJar()).isSameAs(devJar);
+    assertThat(config.getUserJar()).isSameAs(userJar);
+    assertThat(config.getRunners()).containsExactly(
+        RunnerConfigs.getAllRunners().toArray());
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testRunInSingleMode() throws InterruptedException, ExecutionException {
+    final List<String> filterInput = Lists.newArrayList();
+
+    Predicate<String> filterPredicate = new Predicate<String>() {
+        @Override
+      public boolean apply(@Nullable String input) {
+        filterInput.add(input);
+        if (input.equals("ignoredBenchmark")) {
+          return false;
+        }
+        return true;
+      }
+    };
+
+    when(poolProvider.get()).thenReturn(threadPoolExecutor);
+    when(collector.get()).thenReturn(Arrays.asList("module1", "module2", "ignoredBenchmark"));
+
+    ArgumentCaptor<BenchmarkWorkerConfig> workerConfigCapture =
+        ArgumentCaptor.forClass(BenchmarkWorkerConfig.class);
+
+    Mockito.when(benchmarkWorkerFactory.create(workerConfigCapture.capture())).thenReturn(
+        benchmarkWorker);
+
+    ArgumentCaptor<List<BenchmarkRun>> resultCaptor = cast(ArgumentCaptor.forClass(List.class));
+
+    when(reporterFactory.create(resultCaptor.capture(), eq("commit2"))).thenReturn(
+        benchmarkReporter);
+
+
+    Future<WorkResult> workResultFuture1 = cast(mock(Future.class));
+    Future<WorkResult> workResultFuture2 = cast(mock(Future.class));
+
+    when(workResultFuture1.isDone()).thenReturn(false, true);
+    when(workResultFuture2.isDone()).thenReturn(false, true);
+
+    Job job1 = new Job(new JobId("jobId1"), RunnerConfigs.getAllRunners(), 1);
+    job1.addResult(RunnerConfigs.CHROME_LINUX, 1);
+    job1.addResult(RunnerConfigs.FIREFOX_LINUX, 2);
+    job1.addResult(RunnerConfigs.IE10_WIN, 3);
+    job1.addResult(RunnerConfigs.IE11_WIN, 4);
+    WorkResult workResult1 = new WorkResult(job1);
+    when(workResultFuture1.get()).thenReturn(workResult1);
+    Job job2 = new Job(new JobId("jobId2"), RunnerConfigs.getAllRunners(), 2);
+    job2.addResult(RunnerConfigs.CHROME_LINUX, 5);
+    job2.addResult(RunnerConfigs.FIREFOX_LINUX,6);
+    job2.addResult(RunnerConfigs.IE10_WIN, 7);
+    job2.addResult(RunnerConfigs.IE11_WIN, 8);
+    WorkResult workResult2 = new WorkResult(job2);
+    when(workResultFuture2.get()).thenReturn(workResult2);
+
+    when(threadPoolExecutor.submit(benchmarkWorker)).thenReturn(workResultFuture1, workResultFuture2);
+
+    manager = new Manager(collector, benchmarkWorkerFactory, poolProvider, reporterFactory, false,
+        commitReader, errorReporter, filterPredicate, false, false, devJar, userJar, "") {
+
+        @Override
+      void sleepWaitingForJobs() throws InterruptedException {
+      }
+
+        @Override
+      void printOutput(String output) {
+        assertThat(output).isEqualTo("Results:\n" + "  module1\n"
+            + "    linux firefox: 2.000000 runs/second\n"
+            + "    linux chrome: 1.000000 runs/second\n"
+            + "    windows ie IE10: 3.000000 runs/second\n"
+            + "    windows ie IE11: 4.000000 runs/second\n" + "  module2\n"
+            + "    linux firefox: 6.000000 runs/second\n"
+            + "    linux chrome: 5.000000 runs/second\n"
+            + "    windows ie IE10: 7.000000 runs/second\n"
+            + "    windows ie IE11: 8.000000 runs/second\n");
+      }
+    };
+
+    manager.execute();
+
+    verifyZeroInteractions(errorReporter);
+    verifyZeroInteractions(benchmarkReporter);
+
+  }
+}
diff --git a/cli/src/test/java/com/google/j2cl/benchmark/cli/RunZipTest.java b/cli/src/test/java/com/google/j2cl/benchmark/cli/RunZipTest.java
new file mode 100644
index 0000000..0b4d1bc
--- /dev/null
+++ b/cli/src/test/java/com/google/j2cl/benchmark/cli/RunZipTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 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.j2cl.benchmark.cli;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import com.google.j2cl.benchmark.common.runner.Job;
+import com.google.j2cl.benchmark.common.runner.JobId;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigs;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.List;
+
+/**
+ * Test for {@link RunZip}.
+ */
+public class RunZipTest {
+  private BenchmarkUploader benchmarkUploader;
+  private RunZip runZip;
+
+  private PrintStream sysout;
+  private ByteArrayOutputStream outputBytes;
+
+  @Before
+  public void before() {
+    sysout = System.out;
+    benchmarkUploader = mock(BenchmarkUploader.class);
+
+    runZip = new RunZip() {
+      @Override
+      BenchmarkUploader createBenchmarkUploader(String serverUrl, File zip,
+          List<RunnerConfig> runnerConfigs) {
+        return benchmarkUploader;
+      }
+    };
+
+    outputBytes = new ByteArrayOutputStream();
+    System.setOut(new PrintStream(outputBytes));
+  }
+
+  @After
+  public void after() {
+    System.setOut(sysout);
+  }
+
+  @Test
+  public void testSimpleUpload() throws IOException, InterruptedException {
+    String[] args = new String[] {
+        "-benchmark",
+        "foo.zip",
+        "-runnerServerUrl",
+        "http://foo.bar/"
+    };
+
+    Job job = new Job(new JobId("id1"), Lists.newArrayList(RunnerConfigs.CHROME_LINUX), 1);
+    job.addResult(RunnerConfigs.CHROME_LINUX, 2.0);
+    when(benchmarkUploader.run(true)).thenReturn(job);
+
+    runZip.doMain(args);
+    String output = outputBytes.toString("UTF8");
+    assertThat(output).isEqualTo("Results:\n" + "linux chrome: 2.0\n");
+  }
+}
diff --git a/compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/ShouldNotBeIncludedBenchmark.gwt.xml b/cli/src/test/resources/collector-test/com/google/gwt/benchmark/ShouldNotBeIncludedBenchmark.gwt.xml
similarity index 100%
rename from compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/ShouldNotBeIncludedBenchmark.gwt.xml
rename to cli/src/test/resources/collector-test/com/google/gwt/benchmark/ShouldNotBeIncludedBenchmark.gwt.xml
diff --git a/compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/IgnoredTest1.gwt.xml b/cli/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/IgnoredTest1.gwt.xml
similarity index 100%
rename from compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/IgnoredTest1.gwt.xml
rename to cli/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/IgnoredTest1.gwt.xml
diff --git a/compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/TestBenchmark.gwt.xml b/cli/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/TestBenchmark.gwt.xml
similarity index 100%
rename from compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/TestBenchmark.gwt.xml
rename to cli/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/TestBenchmark.gwt.xml
diff --git a/compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/otherfileBenchmark.txt b/cli/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/otherfileBenchmark.txt
similarity index 100%
rename from compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/otherfileBenchmark.txt
rename to cli/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/otherfileBenchmark.txt
diff --git a/compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/package/IgnoredTest2.gwt.xml b/cli/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/package/IgnoredTest2.gwt.xml
similarity index 100%
rename from compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/package/IgnoredTest2.gwt.xml
rename to cli/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/package/IgnoredTest2.gwt.xml
diff --git a/compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/package/TestBenchmark2.gwt.xml b/cli/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/package/TestBenchmark2.gwt.xml
similarity index 100%
rename from compileserver/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/package/TestBenchmark2.gwt.xml
rename to cli/src/test/resources/collector-test/com/google/gwt/benchmark/benchmarks/package/TestBenchmark2.gwt.xml
diff --git a/compileserver/src/test/resources/scripts-fail/buildSDK b/cli/src/test/resources/scripts-fail/buildSDK
similarity index 100%
rename from compileserver/src/test/resources/scripts-fail/buildSDK
rename to cli/src/test/resources/scripts-fail/buildSDK
diff --git a/compileserver/src/test/resources/scripts-fail/checkout b/cli/src/test/resources/scripts-fail/checkout
similarity index 100%
rename from compileserver/src/test/resources/scripts-fail/checkout
rename to cli/src/test/resources/scripts-fail/checkout
diff --git a/compileserver/src/test/resources/scripts-fail/commitDate b/cli/src/test/resources/scripts-fail/commitDate
similarity index 100%
rename from compileserver/src/test/resources/scripts-fail/commitDate
rename to cli/src/test/resources/scripts-fail/commitDate
diff --git a/compileserver/src/test/resources/scripts-fail/commitId b/cli/src/test/resources/scripts-fail/commitId
similarity index 100%
rename from compileserver/src/test/resources/scripts-fail/commitId
rename to cli/src/test/resources/scripts-fail/commitId
diff --git a/compileserver/src/test/resources/scripts-fail/compileModule b/cli/src/test/resources/scripts-fail/compileModule
similarity index 100%
rename from compileserver/src/test/resources/scripts-fail/compileModule
rename to cli/src/test/resources/scripts-fail/compileModule
diff --git a/compileserver/src/test/resources/scripts-fail/maybe_checkout_next_commit b/cli/src/test/resources/scripts-fail/maybe_checkout_next_commit
similarity index 100%
rename from compileserver/src/test/resources/scripts-fail/maybe_checkout_next_commit
rename to cli/src/test/resources/scripts-fail/maybe_checkout_next_commit
diff --git a/compileserver/src/test/resources/scripts-working/buildSDK b/cli/src/test/resources/scripts-working/buildSDK
similarity index 100%
rename from compileserver/src/test/resources/scripts-working/buildSDK
rename to cli/src/test/resources/scripts-working/buildSDK
diff --git a/compileserver/src/test/resources/scripts-working/checkout b/cli/src/test/resources/scripts-working/checkout
similarity index 100%
rename from compileserver/src/test/resources/scripts-working/checkout
rename to cli/src/test/resources/scripts-working/checkout
diff --git a/compileserver/src/test/resources/scripts-working/commitDate b/cli/src/test/resources/scripts-working/commitDate
similarity index 100%
rename from compileserver/src/test/resources/scripts-working/commitDate
rename to cli/src/test/resources/scripts-working/commitDate
diff --git a/compileserver/src/test/resources/scripts-working/commitId b/cli/src/test/resources/scripts-working/commitId
similarity index 100%
rename from compileserver/src/test/resources/scripts-working/commitId
rename to cli/src/test/resources/scripts-working/commitId
diff --git a/cli/src/test/resources/scripts-working/compileModule b/cli/src/test/resources/scripts-working/compileModule
new file mode 100755
index 0000000..5270ed0
--- /dev/null
+++ b/cli/src/test/resources/scripts-working/compileModule
@@ -0,0 +1,22 @@
+#!/bin/bash
+set -e
+
+if [[ -z "${1}" || -z "${2}" || -z "${3}" || -z "${4}" || -z "${5}" ]]; then
+  # Not sending error to &2 since GWT compiler does not do that either
+  echo "usage: compileModule moduleName dev.jar user.jar benchmark_src output_dir"
+  exit 1
+fi
+
+MODULE_NAME=${1}
+shift
+GWT_DEV_JAR=${1}
+shift
+GWT_USER_JAR=${1}
+shift
+BENCHMARKS_SRC=${1}
+shift
+OUTPUT_DIR=${1}
+shift
+
+#Echo back all the parameters so that we can verify them in test
+echo "${MODULE_NAME};${GWT_DEV_JAR};${GWT_USER_JAR};${BENCHMARKS_SRC};${OUTPUT_DIR};${@};" > target/test-out
diff --git a/compileserver/src/test/resources/scripts-working/maybe_checkout_next_commit b/cli/src/test/resources/scripts-working/maybe_checkout_next_commit
similarity index 100%
rename from compileserver/src/test/resources/scripts-working/maybe_checkout_next_commit
rename to cli/src/test/resources/scripts-working/maybe_checkout_next_commit
diff --git a/common/pom.xml b/common/pom.xml
index 04a456f..5b6601d 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -16,58 +16,35 @@
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <gwtversion>2.6.1</gwtversion>
   </properties>
-  
-  
 
   <dependencies>
     <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-user</artifactId>
-      <version>${gwtversion}</version>
-    </dependency>
-    <dependency>
-      <groupId>javax.validation</groupId>
-      <artifactId>validation-api</artifactId>
-      <version>1.0.0.GA</version>
-    </dependency>
-    <dependency>
-      <groupId>javax.validation</groupId>
-      <artifactId>validation-api</artifactId>
-      <version>1.0.0.GA</version>
-      <classifier>sources</classifier>
-    </dependency>
-    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
-      <version>4.11</version>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>com.google.api-client</groupId>
-      <artifactId>google-api-client-jackson2</artifactId>
-      <version>1.20.0</version>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>com.google.api-client</groupId>
-      <artifactId>google-api-client</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.oauth-client</groupId>
-      <artifactId>google-oauth-client</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.oauth-client</groupId>
-      <artifactId>google-oauth-client-java6</artifactId>
-      <version>1.20.0</version>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
-      <version>18.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
     </dependency>
   </dependencies>
 
@@ -88,56 +65,15 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>2.3</version>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
-        <version>2.5.1</version>
         <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.7</source>
-          <target>1.7</target>
-        </configuration>
       </plugin>
 
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-eclipse-plugin</artifactId>
-        <version>2.8</version>
-
-        <configuration>
-          <downloadSources>true</downloadSources>
-          <downloadJavadocs>false</downloadJavadocs>
-          <buildOutputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</buildOutputDirectory>
-          <projectnatures>
-            <projectnature>org.eclipse.jdt.core.javanature</projectnature>
-            <projectnature>com.google.gdt.eclipse.core.webAppNature</projectnature>
-            <nature>com.google.gwt.eclipse.core.gwtNature</nature>
-          </projectnatures>
-          <buildcommands>
-            <buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
-            <buildcommand>com.google.gdt.eclipse.core.webAppProjectValidator</buildcommand>
-            <buildcommand>com.google.gwt.eclipse.core.gwtProjectValidator</buildcommand>
-          </buildcommands>
-          <classpathContainers>
-            <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
-            <classpathContainer>com.google.gwt.eclipse.core.GWT_CONTAINER</classpathContainer>
-          </classpathContainers>
-          <excludes>
-            <exclude>com.google.gwt:gwt-servlet</exclude>
-            <exclude>com.google.gwt:gwt-user</exclude>
-            <exclude>com.google.gwt:gwt-dev</exclude>
-            <exclude>javax.validation:validation-api</exclude>
-          </excludes>
-        </configuration>
       </plugin>
 
     </plugins>
diff --git a/common/src/main/java/com/google/gwt/benchmark/common/Common.gwt.xml b/common/src/main/java/com/google/gwt/benchmark/common/Common.gwt.xml
deleted file mode 100644
index cd0107d..0000000
--- a/common/src/main/java/com/google/gwt/benchmark/common/Common.gwt.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<!--                                                                        -->
-<!-- Copyright 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   -->
-<!-- may obtain a copy of the License at                                    -->
-<!--                                                                        -->
-<!-- http://www.apache.org/licenses/LICENSE-2.0                             -->
-<!--                                                                        -->
-<!-- Unless required by applicable law or agreed to in writing, software    -->
-<!-- distributed under the License is distributed on an "AS IS" BASIS,      -->
-<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        -->
-<!-- implied. License for the specific language governing permissions and   -->
-<!-- limitations under the License.                                         -->
-
-<module>
-  <inherits name="com.google.gwt.core.Core" />
-  <inherits name="com.google.web.bindery.autobean.AutoBean"/>
-  <source path="client" />
-  <source path="shared" />
-</module>
\ No newline at end of file
diff --git a/common/src/main/java/com/google/gwt/benchmark/common/shared/json/BenchmarkResultJson.java b/common/src/main/java/com/google/gwt/benchmark/common/shared/json/BenchmarkResultJson.java
deleted file mode 100644
index 03d71ed..0000000
--- a/common/src/main/java/com/google/gwt/benchmark/common/shared/json/BenchmarkResultJson.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.common.shared.json;
-
-
-/**
- * A JSON representation of a benchmark result.
- */
-public interface BenchmarkResultJson {
-
-  public String getBenchmarkName();
-
-  public void setBenchmarkName(String benchmarkName);
-
-  public void setRunnerId(String runnerId);
-
-  public String getRunnerId();
-
-  public double getRunsPerSecond();
-
-  public void setRunsPerSecond(double runsPerSecond);
-}
diff --git a/common/src/main/java/com/google/gwt/benchmark/common/shared/json/BenchmarkRunJson.java b/common/src/main/java/com/google/gwt/benchmark/common/shared/json/BenchmarkRunJson.java
deleted file mode 100644
index 95968eb..0000000
--- a/common/src/main/java/com/google/gwt/benchmark/common/shared/json/BenchmarkRunJson.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.common.shared.json;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * A JSON representation of a set of benchmark runs.
- */
-public interface BenchmarkRunJson {
-
-  String getCommitId();
-
-  /**
-   * Get the commit time of the patch in milliseconds since 1970.
-   * <p>
-   * Note: This is not the author time of the commit, but the time it has been merged into the
-   * repository
-   */
-  double getCommitTimeMsEpoch();
-
-  Map<String, List<BenchmarkResultJson>> getResultByBenchmarkName();
-
-  void setResultByBenchmarkName(Map<String, List<BenchmarkResultJson>> results);
-
-  void setCommitId(String commitId);
-
-  /**
-   * Set the commit time of the patch in milliseconds since 1970.
-   * <p>
-   * Note: This is not the author time of the commit, but the time it has been merged into the
-   * repository
-   */
-  void setCommitTimeMsEpoch(double commitMsEpoch);
-}
diff --git a/common/src/main/java/com/google/gwt/benchmark/common/shared/json/JsonFactory.java b/common/src/main/java/com/google/gwt/benchmark/common/shared/json/JsonFactory.java
deleted file mode 100644
index 8162731..0000000
--- a/common/src/main/java/com/google/gwt/benchmark/common/shared/json/JsonFactory.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.common.shared.json;
-
-import com.google.gwt.core.shared.GWT;
-import com.google.web.bindery.autobean.shared.AutoBean;
-import com.google.web.bindery.autobean.shared.AutoBeanFactory;
-import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;
-
-/**
- * Factory to create JSON representations.
- */
-public final class JsonFactory {
-
-  public interface Factory extends AutoBeanFactory {
-    AutoBean<BenchmarkResultJson> result();
-
-    AutoBean<BenchmarkRunJson> run();
-  }
-
-  private static final Factory INSTANCE = (GWT.isClient() ? (Factory) GWT.create(Factory.class)
-      : AutoBeanFactorySource.create(Factory.class));
-
-  public static Factory get() {
-    return INSTANCE;
-  }
-
-  public JsonFactory() {
-  }
-}
diff --git a/common/src/main/java/com/google/j2cl/benchmark/common/runner/Job.java b/common/src/main/java/com/google/j2cl/benchmark/common/runner/Job.java
new file mode 100644
index 0000000..521c485
--- /dev/null
+++ b/common/src/main/java/com/google/j2cl/benchmark/common/runner/Job.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 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.j2cl.benchmark.common.runner;
+
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A job represents a benchmark being run through different kind of browsers.
+ */
+public class Job {
+  private final JobId jobId;
+  private Status status = Status.CREATED;
+  private int counter;
+  private int failedCounter;
+  private final int expectedResults;
+
+  private Map<String, JobResult> jobResultsByRunnerId = new HashMap<>();
+  private List<RunnerConfig> runnerConfigs;
+  private File folder;
+  private FailReason failReason;
+  private final long creationTimeInMsEpoch;
+
+  public enum FailReason {
+    CAN_NOT_EXTRACT_ZIP, AT_LEAST_ONE_BENCHMARK_FAILED_TO_RUN
+  }
+
+  public enum Status {
+    CREATED, SUBMITTED, RUNNING, FINISHED, FAILED
+  }
+
+  public Job(JobId id, List<RunnerConfig> runnerConfigs, long creationTimeInMsEpoch) {
+    this.jobId = id;
+    this.creationTimeInMsEpoch = creationTimeInMsEpoch;
+    this.runnerConfigs = runnerConfigs;
+    for (RunnerConfig runnerConfig : runnerConfigs) {
+      jobResultsByRunnerId.put(runnerConfig.toString(), new JobResult(runnerConfig));
+    }
+    this.expectedResults = runnerConfigs.size();
+  }
+
+  public JobId getJobId() {
+    return jobId;
+  }
+
+  public Status getStatus() {
+    return status;
+  }
+
+  public void addResult(RunnerConfig config, double result) {
+    JobResult jobResult = getJobResult(config);
+    if (jobResult.isRan()) {
+      throw new IllegalStateException();
+    }
+    jobResult.setSucceded(true);
+    jobResult.setResult(result);
+    jobResult.setRan(true);
+    counter++;
+
+    maybeChangeStatus();
+  }
+
+  public void setRunFailed(RunnerConfig config, String reason) {
+    JobResult jobResult = getJobResult(config);
+    if (jobResult.isRan()) {
+      throw new IllegalStateException();
+    }
+
+    jobResult.setSucceded(false);
+    jobResult.setErrorMessage(reason);
+    jobResult.setRan(true);
+    this.failReason = FailReason.AT_LEAST_ONE_BENCHMARK_FAILED_TO_RUN;
+
+    failedCounter++;
+    maybeChangeStatus();
+  }
+
+  private JobResult getJobResult(RunnerConfig config) {
+    JobResult jobResult = jobResultsByRunnerId.get(config.toString());
+
+    if (jobResult == null) {
+      throw new IllegalStateException();
+    }
+    return jobResult;
+  }
+
+  private void maybeChangeStatus() {
+    if (counter == expectedResults) {
+      status = Status.FINISHED;
+    } else if (counter + failedCounter == expectedResults) {
+      status = Status.FAILED;
+    } else {
+      if (status == Status.CREATED) {
+        status = Status.RUNNING;
+      }
+    }
+  }
+
+  @Override
+  public Job clone() {
+    Job other = new Job(jobId, new ArrayList<>(runnerConfigs), creationTimeInMsEpoch);
+     other.counter = counter;
+     other.failedCounter = failedCounter;
+     other.status = status;
+     other.folder = folder;
+     other.failReason = failReason;
+
+     for (Entry<String, JobResult> entry : jobResultsByRunnerId.entrySet()) {
+       other.jobResultsByRunnerId.put(entry.getKey(), entry.getValue().clone());
+     }
+
+     return other;
+  }
+
+  public boolean isOld(long currentTimeInMsEpoch) {
+    long oneHour = 1000l * 60 * 60;
+    if (currentTimeInMsEpoch - oneHour > creationTimeInMsEpoch) {
+      return true;
+    }
+    return false;
+  }
+
+  public JobResult getResult(RunnerConfig config) {
+    return getJobResult(config);
+  }
+
+  public void setFolder(File folder) {
+    this.folder = folder;
+  }
+
+  public File getFolder() {
+    return folder;
+  }
+
+  public FailReason getFailReason() {
+    return failReason;
+  }
+
+  public boolean isDone() {
+    for (JobResult r : jobResultsByRunnerId.values()) {
+      if (!r.isRan()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public List<JobResult> getJobResults() {
+    return Lists.newArrayList(jobResultsByRunnerId.values());
+  }
+
+  public boolean isSucceeded() {
+    return status == Status.FINISHED;
+  }
+
+  public void setFailed(FailReason failReason) {
+    this.failReason = failReason;
+    status = Status.FAILED;
+  }
+}
diff --git a/common/src/main/java/com/google/j2cl/benchmark/common/runner/JobId.java b/common/src/main/java/com/google/j2cl/benchmark/common/runner/JobId.java
new file mode 100644
index 0000000..64acfae
--- /dev/null
+++ b/common/src/main/java/com/google/j2cl/benchmark/common/runner/JobId.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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.j2cl.benchmark.common.runner;
+
+import com.google.common.base.Objects;
+
+/**
+ * A unique id for a {@link Job}.
+ */
+public class JobId {
+  private final String id;
+
+  public JobId(String id) {
+    this.id = id;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  @Override
+  public String toString() {
+    return "JobId [id=" + id + "]";
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == null) {
+      return false;
+    }
+
+    if (!(other instanceof JobId)) {
+      return false;
+    }
+    JobId jobId = (JobId) other;
+
+    return Objects.equal(id, jobId.id);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(id);
+  }
+}
diff --git a/common/src/main/java/com/google/j2cl/benchmark/common/runner/JobResult.java b/common/src/main/java/com/google/j2cl/benchmark/common/runner/JobResult.java
new file mode 100644
index 0000000..455930d
--- /dev/null
+++ b/common/src/main/java/com/google/j2cl/benchmark/common/runner/JobResult.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 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.j2cl.benchmark.common.runner;
+
+/**
+ * Represents one result of a {@link Job}. There are results for each browser / configuration
+ * being run.
+ */
+public class JobResult {
+  private String errorMessage;
+  private boolean succeded;
+  private double result;
+  private boolean ran;
+  private final RunnerConfig runnerConfig;
+
+  public JobResult(RunnerConfig runnerConfig) {
+    this.runnerConfig = runnerConfig;
+  }
+
+  public void setErrorMessage(String errorMessage) {
+    this.errorMessage = errorMessage;
+  }
+
+  public String getErrorMessage() {
+    return errorMessage;
+  }
+
+  public void setResult(double result) {
+    this.result = result;
+  }
+
+  public double getResult() {
+    return result;
+  }
+
+  public boolean isSucceded() {
+    return succeded;
+  }
+
+  public void setSucceded(boolean succeded) {
+    this.succeded = succeded;
+  }
+
+  public void setRan(boolean ran) {
+    this.ran = ran;
+  }
+
+  public boolean isRan() {
+    return ran;
+  }
+
+  @Override
+  public JobResult clone() {
+    JobResult jobResult = new JobResult(runnerConfig);
+    jobResult.errorMessage = errorMessage;
+    jobResult.ran = ran;
+    jobResult.result = result;
+    jobResult.succeded = succeded;
+    return jobResult;
+  }
+
+  @Override
+  public String toString() {
+    return "JobResult [errorMessage=" + errorMessage + ", succeded=" + succeded + ", result="
+        + result + ", ran=" + ran + "]";
+  }
+
+  public RunnerConfig getConfig() {
+    return runnerConfig;
+  }
+}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/Runner.java b/common/src/main/java/com/google/j2cl/benchmark/common/runner/Runner.java
similarity index 96%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/Runner.java
rename to common/src/main/java/com/google/j2cl/benchmark/common/runner/Runner.java
index 2057a87..09fc4c9 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/Runner.java
+++ b/common/src/main/java/com/google/j2cl/benchmark/common/runner/Runner.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.common.runner;
 
 /**
  * A Runner executes a compiled benchmark and makes the result available.
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/RunnerConfig.java b/common/src/main/java/com/google/j2cl/benchmark/common/runner/RunnerConfig.java
similarity index 96%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/RunnerConfig.java
rename to common/src/main/java/com/google/j2cl/benchmark/common/runner/RunnerConfig.java
index a916388..75463fb 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/RunnerConfig.java
+++ b/common/src/main/java/com/google/j2cl/benchmark/common/runner/RunnerConfig.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.common.runner;
 
 /**
  * A RunnerConfig describes all attributes of a runner like browser, OS and version.
diff --git a/common/src/main/java/com/google/j2cl/benchmark/common/runner/RunnerConfigJson.java b/common/src/main/java/com/google/j2cl/benchmark/common/runner/RunnerConfigJson.java
new file mode 100644
index 0000000..f3d2e0f
--- /dev/null
+++ b/common/src/main/java/com/google/j2cl/benchmark/common/runner/RunnerConfigJson.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.j2cl.benchmark.common.runner;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import java.lang.reflect.Type;
+
+/**
+ * A Json serializer and deserializer for {@link RunnerConfig}.
+ */
+public class RunnerConfigJson
+    implements JsonDeserializer<RunnerConfig>, JsonSerializer<RunnerConfig> {
+  @Override
+  public RunnerConfig deserialize(JsonElement json, Type typeOfT,
+      JsonDeserializationContext context) throws JsonParseException {
+    String string = json.getAsString();
+    return RunnerConfigs.fromString(string);
+  }
+
+  @Override
+  public JsonElement serialize(RunnerConfig src, Type typeOfSrc, JsonSerializationContext context) {
+    return new JsonPrimitive(src.toString());
+  }
+}
+
diff --git a/common/src/main/java/com/google/j2cl/benchmark/common/runner/RunnerConfigs.java b/common/src/main/java/com/google/j2cl/benchmark/common/runner/RunnerConfigs.java
new file mode 100644
index 0000000..5033a96
--- /dev/null
+++ b/common/src/main/java/com/google/j2cl/benchmark/common/runner/RunnerConfigs.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 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.j2cl.benchmark.common.runner;
+
+import com.google.common.base.Objects;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig.Browser;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig.OS;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Global Object containing all available {@link RunnerConfig RunnerConfigs}.
+ */
+public final class RunnerConfigs {
+
+  private static class RunnerConfigImpl implements RunnerConfig {
+
+    private Browser browser;
+    private OS os;
+    private String version;
+
+    public RunnerConfigImpl(Browser browser, OS os, String version) {
+      this.browser = browser;
+      this.os = os;
+      this.version = version;
+    }
+
+    @Override
+    public Browser getBrowser() {
+      return browser;
+    }
+
+    @Override
+    public OS getOS() {
+      return os;
+    }
+
+    @Override
+    public String getBrowserVersion() {
+      return version;
+    }
+
+    @Override
+    public String toString() {
+      String id =  os + " " + browser;
+      if (!"".equals(version)) {
+        id += " " + version;
+      }
+      return id;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(browser, os, version);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (other == null) {
+        return false;
+      }
+
+      if (!(other instanceof RunnerConfig)) {
+        return false;
+      }
+      RunnerConfig otherConfig = (RunnerConfig) other;
+
+      if (!Objects.equal(os, otherConfig.getOS())) {
+        return false;
+      }
+
+      if (!Objects.equal(browser, otherConfig.getBrowser())) {
+        return false;
+      }
+
+      if (!Objects.equal(version, otherConfig.getBrowserVersion())) {
+        return false;
+      }
+      return true;
+    }
+  }
+
+  /** Chrome on linux */
+  public static final RunnerConfig CHROME_LINUX =
+      new RunnerConfigImpl(Browser.CHROME, OS.LINUX, "");
+  /** Firefox on linux */
+  public static final RunnerConfig FIREFOX_LINUX =
+      new RunnerConfigImpl(Browser.FIREFOX, OS.LINUX, "");
+  /** IE11 on windows */
+  public static final RunnerConfig IE11_WIN =
+      new RunnerConfigImpl(Browser.INTERNET_EXPLORER, OS.WINDOWS, RunnerConfig.IE_11_VERSION);
+  /** IE10 on windows */
+  public static final RunnerConfig IE10_WIN =
+      new RunnerConfigImpl(Browser.INTERNET_EXPLORER, OS.WINDOWS, RunnerConfig.IE_10_VERSION);
+
+
+  public static List<RunnerConfig> getAllRunners() {
+    return Arrays.asList(
+        RunnerConfigs.FIREFOX_LINUX,
+        RunnerConfigs.CHROME_LINUX,
+        RunnerConfigs.IE10_WIN,
+        RunnerConfigs.IE11_WIN);
+  }
+
+  public static RunnerConfig fromString(String s) {
+    switch(s) {
+      case "linux chrome":
+        return CHROME_LINUX;
+      case "linux firefox":
+        return FIREFOX_LINUX;
+      case "windows ie IE10":
+        return IE10_WIN;
+      case "windows ie IE11":
+        return IE11_WIN;
+      default:
+        throw new IllegalArgumentException(s);
+    }
+  }
+
+  private RunnerConfigs() {}
+}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/Util.java b/common/src/main/java/com/google/j2cl/benchmark/common/util/Util.java
similarity index 92%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/Util.java
rename to common/src/main/java/com/google/j2cl/benchmark/common/util/Util.java
index 91af3c4..8b49d80 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/Util.java
+++ b/common/src/main/java/com/google/j2cl/benchmark/common/util/Util.java
@@ -11,13 +11,16 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.runners.settings;
+package com.google.j2cl.benchmark.common.util;
 
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.util.Enumeration;
 
+/**
+ * Util methods that do not fit elsewhere.
+ */
 public class Util {
   public static InetAddress getFirstNonLoopbackAddress() throws Exception {
     Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
diff --git a/common/src/main/java/com/google/j2cl/benchmark/common/util/ZipUtil.java b/common/src/main/java/com/google/j2cl/benchmark/common/util/ZipUtil.java
new file mode 100644
index 0000000..b9e84b2
--- /dev/null
+++ b/common/src/main/java/com/google/j2cl/benchmark/common/util/ZipUtil.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 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.j2cl.benchmark.common.util;
+
+import com.google.common.collect.Lists;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Utility class to deal with zip files.
+ */
+public class ZipUtil {
+
+  public static void unzip(File zipFile, File destDir) throws IOException {
+    if (!destDir.exists()) {
+      if (!destDir.mkdirs()) {
+        throw new IOException("Can not create dir: " + destDir.getAbsolutePath());
+      }
+    }
+    ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFile));
+    ZipEntry entry = zipIn.getNextEntry();
+
+    while (entry != null) {
+      String filePath = destDir + File.separator + entry.getName();
+      if (!entry.isDirectory()) {
+        File file = new File(filePath);
+        file.getParentFile().mkdirs();
+        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));) {
+          IOUtils.copy(zipIn, bos);
+        }
+      } else {
+        new File(filePath).mkdir();
+      }
+      zipIn.closeEntry();
+      entry = zipIn.getNextEntry();
+    }
+    zipIn.close();
+  }
+
+  public static File zipFolder(File folder, String fileName) throws IOException {
+    File zipFile = File.createTempFile(fileName, ".zip");
+    try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
+      for (File f : listAllFiles(folder)) {
+        addToZipFile(folder, f, zos);
+      }
+    }
+    return zipFile;
+  }
+
+  private static List<File> listAllFiles(File outputDir) {
+    List<File> files = Lists.newArrayList();
+    listAllFiles(outputDir, files);
+    return files;
+  }
+
+  private static void listAllFiles(File current, List<File> files) {
+    if (current.isDirectory()) {
+      for( File f : current.listFiles()) {
+        listAllFiles(f, files);
+      }
+    } else if (current.isFile()) {
+      files.add(current);
+    }
+  }
+
+  private static void addToZipFile(File baseDir, File fileToWrite, ZipOutputStream zos)
+      throws FileNotFoundException, IOException {
+    String relativeName =
+        fileToWrite.getAbsolutePath().substring(baseDir.getAbsolutePath().length() + 1);
+    FileInputStream fis = new FileInputStream(fileToWrite);
+    ZipEntry zipEntry = new ZipEntry(relativeName);
+    zos.putNextEntry(zipEntry);
+    IOUtils.copy(fis, zos);
+    zos.closeEntry();
+    fis.close();
+  }
+}
diff --git a/common/src/test/java/com/google/j2cl/benchmark/common/util/ZipUtilTest.java b/common/src/test/java/com/google/j2cl/benchmark/common/util/ZipUtilTest.java
new file mode 100644
index 0000000..3a5ee4e
--- /dev/null
+++ b/common/src/test/java/com/google/j2cl/benchmark/common/util/ZipUtilTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 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.j2cl.benchmark.common.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.FileWriteMode;
+import com.google.common.io.Files;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Test for {@link ZipUtil}.
+ */
+public class ZipUtilTest {
+
+  private File tempDir;
+  private File barDir;
+  private File bazFile;
+  private File fooFile;
+  private File zipFile;
+  private File unzipDir;
+
+  @Before
+  public void before() throws IOException {
+    tempDir = Files.createTempDir();
+    fooFile = new File(tempDir, "foo");
+    Files.asCharSink(fooFile, Charsets.UTF_8, FileWriteMode.APPEND).write("foo");
+    barDir = new File(tempDir, "bar/");
+    barDir.mkdir();
+    bazFile = new File(barDir, "baz");
+    Files.asCharSink(bazFile, Charsets.UTF_8, FileWriteMode.APPEND).write("baz");
+  }
+
+  @After
+  public void after() {
+    FileUtils.deleteQuietly(tempDir);
+    FileUtils.deleteQuietly(zipFile);
+    FileUtils.deleteQuietly(unzipDir);
+  }
+
+  @Test
+  public void zipAndUnzipFolder() throws IOException {
+    zipFile = ZipUtil.zipFolder(tempDir, "my_zipped_content");
+    assertThat(zipFile.exists()).isTrue();
+
+    unzipDir = Files.createTempDir();
+
+    assertThat(unzipDir.listFiles().length).isEqualTo(0);
+
+    ZipUtil.unzip(zipFile, unzipDir);
+
+    assertThat(unzipDir.listFiles()).hasLength(2);
+
+    File newBarDir = new File(unzipDir, "bar/");
+    File newFooFile = new File(unzipDir, "foo");
+    File newBazFile = new File(newBarDir, "baz");
+    assertThat(unzipDir.listFiles())
+        .asList()
+        .containsExactly(newFooFile, newBarDir);
+
+    assertThat(newBarDir.listFiles())
+    .asList()
+    .containsExactly(newBazFile);
+
+    assertThat(Files.asCharSource(newFooFile, Charsets.UTF_8).read()).isEqualTo("foo");
+    assertThat(Files.asCharSource(newBazFile, Charsets.UTF_8).read()).isEqualTo("baz");
+  }
+
+}
+
diff --git a/compileserver/config/config.example b/compileserver/config/config.example
deleted file mode 100644
index 273a527..0000000
--- a/compileserver/config/config.example
+++ /dev/null
@@ -1,56 +0,0 @@
-## The compile server uses a config file to read in all of its settings.
-## The file is passed to the container running the compile server using a
-## environment variable -DconfigFile=<absolute path>.
-##
-
-## Location settings
-
-# Location of the benchmarks (benchmarks/ in the checkout)
-benchmarksDirectory = /usr/local/temp/gwt-benchmarks/benchmarks/
-
-# The output folder for the GWT compiler, this folder is also served up by jetty
-compileOutputDir = /usr/local/temp/gwt-benchmarks/compileserver/target/benchmark-js/
-
-# The directory where the compile server can store information (last commit)
-persistenceDir = /usr/local/temp/persistenceDir/
-
-# The location of the GWT SDK source
-gwtSourceLocation = /usr/local/temp/gwt/
-
-## Runners
-# Url of the selenium hub
-seleniumHubUrl = http://myhost:4444/wd/hub
-
-# How many GWT compiles / benchmark execution should happen in parallel
-threadPoolSize = 5
-
-# Mode of the benchmarking system
-# Currently the system only supports server mode. In server mode the system will
-# track a repository and recompile and run the benchmarks for every change of 
-# the tracked repository.
-mode = server
-
-## Report settings
-# should we report results
-reportResuts = false
-
-# Url of the benchmark dashboard
-reporterUrl = https://gwt-bench.appspot.com/post_result
-
-# The secret that protects the dashboard
-reporterSecret = <mysecret>
-
-
-## Mail settings
-mail.to = peopletonotify@mydomain.com
-mail.from = mygmail@gmail.com
-mail.host = smtp.gmail.com
-mail.username = mygmail@gmail.com
-mail.password = my password
-
-## Misc
-# the html file used to serve up benchmarks
-moduleTemplate = runner_template.html
-
-# The port on which the benchmarking system is running
-servletContainerPort = 8888
\ No newline at end of file
diff --git a/compileserver/pom.xml b/compileserver/pom.xml
deleted file mode 100644
index 6dc65cf..0000000
--- a/compileserver/pom.xml
+++ /dev/null
@@ -1,210 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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/xsd/maven-4.0.0.xsd">
-
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>com.google.gwt.benchmark</groupId>
-  <artifactId>gwt-benchmark-compileserver</artifactId>
-  <version>1.0-SNAPSHOT</version>
-  <packaging>war</packaging>
-
-  <properties>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <gwtversion>2.6.0</gwtversion>
-  </properties>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gwt.benchmark</groupId>
-      <artifactId>gwt-benchmark-common</artifactId>
-      <version>1.0-SNAPSHOT</version>
-    </dependency>
-    <dependency>
-      <groupId>org.seleniumhq.selenium</groupId>
-      <artifactId>selenium-java</artifactId>
-      <version>2.40.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.inject.extensions</groupId>
-      <artifactId>guice-servlet</artifactId>
-      <version>3.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-user</artifactId>
-      <version>${gwtversion}</version>
-    </dependency>
-    <dependency>
-      <groupId>javax.validation</groupId>
-      <artifactId>validation-api</artifactId>
-      <version>1.0.0.GA</version>
-    </dependency>
-    <dependency>
-      <groupId>javax.validation</groupId>
-      <artifactId>validation-api</artifactId>
-      <version>1.0.0.GA</version>
-      <classifier>sources</classifier>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>4.11</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.mockito</groupId>
-      <artifactId>mockito-all</artifactId>
-      <version>1.9.5</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gwt.inject</groupId>
-      <artifactId>gin</artifactId>
-      <version>2.1.2</version>
-    </dependency>
-    <dependency>
-      <groupId>javax.mail</groupId>
-      <artifactId>mail</artifactId>
-      <version>1.4</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-      <version>9.1.4.v20140401</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gwt.gwtmockito</groupId>
-      <artifactId>gwtmockito</artifactId>
-      <version>1.1.3</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>commons-fileupload</groupId>
-      <artifactId>commons-fileupload</artifactId>
-      <version>1.3.1</version>
-    </dependency>
-    <dependency>
-      <groupId>net.lingala.zip4j</groupId>
-      <artifactId>zip4j</artifactId>
-      <version>1.3.1</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.api-client</groupId>
-      <artifactId>google-api-client-jackson2</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.api-client</groupId>
-      <artifactId>google-api-client</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.oauth-client</groupId>
-      <artifactId>google-oauth-client</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.oauth-client</groupId>
-      <artifactId>google-oauth-client-java6</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gdata</groupId>
-      <artifactId>core</artifactId>
-      <version>1.47.1</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-      <version>18.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.truth</groupId>
-      <artifactId>truth</artifactId>
-      <version>0.25</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <version>2.5.1</version>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.7</source>
-          <target>1.7</target>
-        </configuration>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-war-plugin</artifactId>
-        <version>2.3</version>
-        <configuration>
-          <webResources>
-            <resource>
-              <directory>${basedir}/src/main/webapp/WEB-INF</directory>
-              <filtering>true</filtering>
-              <targetPath>WEB-INF</targetPath>
-            </resource>
-          </webResources>
-        </configuration>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-eclipse-plugin</artifactId>
-        <version>2.8</version>
-        <configuration>
-          <downloadSources>true</downloadSources>
-          <downloadJavadocs>false</downloadJavadocs>
-          <buildOutputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</buildOutputDirectory>
-          <projectnatures>
-            <projectnature>org.eclipse.jdt.core.javanature</projectnature>
-            <projectnature>com.google.gdt.eclipse.core.webAppNature</projectnature>
-            <nature>com.google.gwt.eclipse.core.gwtNature</nature>
-          </projectnatures>
-          <buildcommands>
-            <buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
-            <buildcommand>com.google.gdt.eclipse.core.webAppProjectValidator</buildcommand>
-            <buildcommand>com.google.gwt.eclipse.core.gwtProjectValidator</buildcommand>
-
-          </buildcommands>
-          <classpathContainers>
-            <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
-            <classpathContainer>com.google.gwt.eclipse.core.GWT_CONTAINER</classpathContainer>
-          </classpathContainers>
-          <excludes>
-            <exclude>com.google.gwt:gwt-servlet</exclude>
-            <exclude>com.google.gwt:gwt-user</exclude>
-            <exclude>com.google.gwt:gwt-dev</exclude>
-            <exclude>javax.validation:validation-api</exclude>
-          </excludes>
-        </configuration>
-      </plugin>
-
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>gwt-maven-plugin</artifactId>
-        <version>${gwtversion}</version>
-        <executions>
-          <execution>
-            <configuration>
-              <module>com.google.gwt.benchmark.compileserver.CompileServer</module>
-              <runTarget>index.html</runTarget>
-              <copyWebapp>true</copyWebapp>
-            </configuration>
-            <goals>
-              <goal>compile</goal>
-              <goal>test</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-    </plugins>
-  </build>
-
-</project>
-
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/CompileServer.gwt.xml b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/CompileServer.gwt.xml
deleted file mode 100644
index 2ddef1c..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/CompileServer.gwt.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--                                                                        -->
-<!-- Copyright 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   -->
-<!-- may obtain a copy of the License at                                    -->
-<!--                                                                        -->
-<!-- http://www.apache.org/licenses/LICENSE-2.0                             -->
-<!--                                                                        -->
-<!-- Unless required by applicable law or agreed to in writing, software    -->
-<!-- distributed under the License is distributed on an "AS IS" BASIS,      -->
-<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        -->
-<!-- implied. License for the specific language governing permissions and   -->
-<!-- limitations under the License.                                         -->
-
-<module rename-to="compileserver">
-  <inherits name="com.google.gwt.user.User" />
-  <inherits name="com.google.gwt.inject.Inject"/>
-  <inherits name="com.google.gwt.benchmark.common.Common" />
-
-  <source path="client" />
-  <source path="shared" />
-  <entry-point class="com.google.gwt.benchmark.compileserver.client.CompileServerEntryPoint" />
-
-  <!-- We can remove these with GWT 2.7 since default linker will change -->
-  <add-linker name="xsiframe"/>
-  <set-configuration-property name="devModeRedirectEnabled" value="true"/>
-</module>
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/CompileServerEntryPoint.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/CompileServerEntryPoint.java
deleted file mode 100644
index e5dcdc0..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/CompileServerEntryPoint.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.client;
-
-import com.google.gwt.benchmark.compileserver.client.status.BenchmarkStatusComposite;
-import com.google.gwt.core.client.EntryPoint;
-import com.google.gwt.core.shared.GWT;
-import com.google.gwt.i18n.client.NumberFormat;
-import com.google.gwt.inject.client.AbstractGinModule;
-import com.google.gwt.inject.client.GinModules;
-import com.google.gwt.inject.client.Ginjector;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.inject.Provides;
-
-import javax.inject.Named;
-
-
-/**
- * EntryPoint for compile server UI.
- */
-public class CompileServerEntryPoint implements EntryPoint {
-
-  public static class CompileServerClientModule extends AbstractGinModule {
-
-    @Override
-    protected void configure() {
-    }
-
-    @Provides
-    protected NumberFormat provideNumberFormat() {
-      return NumberFormat.getFormat("#,##0.##");
-    }
-
-    @Provides
-    protected Label providesLabel() {
-      return new Label();
-    }
-
-    @Provides
-    @Named("fileUpload")
-    protected boolean showFileUpload () {
-      return Window.Location.getPath().contains("single.html");
-    }
-  }
-
-  @GinModules(CompileServerClientModule.class)
-  public interface CompileServerInjector extends Ginjector {
-    BenchmarkStatusComposite getBenchmarkListComposite();
-  }
-
-  @Override
-  public void onModuleLoad() {
-    CompileServerInjector injector = GWT.create(CompileServerInjector.class);
-    BenchmarkStatusComposite benchmarkStatusComposite = injector.getBenchmarkListComposite();
-    RootPanel.get().add(benchmarkStatusComposite);
-    benchmarkStatusComposite.start();
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.java
deleted file mode 100644
index 1d5ff65..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.client.status;
-
-import com.google.gwt.benchmark.compileserver.shared.ServiceAsync;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkOverviewEntryDTO;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkOverviewResponseDTO;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkRunDTO;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkRunDTO.State;
-import com.google.gwt.core.shared.GWT;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.i18n.client.NumberFormat;
-import com.google.gwt.resources.client.ClientBundle;
-import com.google.gwt.resources.client.CssResource;
-import com.google.gwt.uibinder.client.UiBinder;
-import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.uibinder.client.UiHandler;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FileUpload;
-import com.google.gwt.user.client.ui.FormPanel;
-import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
-import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.inject.Provider;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * A view that displays the status of the compile server.
- */
-public class BenchmarkStatusComposite extends Composite {
-
-  interface Bundle extends ClientBundle {
-
-    @Source("benchmarkstatus.css")
-    Css css();
-  }
-
-  interface Css extends CssResource {
-    String clickable();
-
-    String statusFatalError();
-
-    String statusOneFailed();
-
-    String statusDone();
-
-    String statusRunning();
-  }
-
-  interface Binder extends UiBinder<Widget, BenchmarkStatusComposite> {
-  }
-
-  private static final Binder uiBinder = GWT.create(Binder.class);
-
-  // Visible for testing
-  static final Bundle bundle = GWT.create(Bundle.class);
-
-  private final NumberFormat format;
-  private final List<HandlerRegistration> handlers = new ArrayList<>();
-  private final Provider<Label> labelProvider;
-  private BenchmarkOverviewResponseDTO result;
-  private boolean running;
-  private final ServiceAsync service;
-
-  @UiField
-  Widget contentContainer;
-
-  @UiField
-  Label errorLabel;
-
-  @UiField
-  Grid grid;
-
-  @UiField
-  Widget loadingLabel;
-
-  @UiField
-  Label statusText;
-
-  @UiField
-  Button startStopButton;
-
-  @UiField
-  FormPanel formPanel;
-
-  @UiField
-  FileUpload fileUpload;
-
-  @UiField
-  Button uploadButton;
-
-  private boolean showFileUpload;
-
-  @Inject
-  public BenchmarkStatusComposite(ServiceAsync service, NumberFormat format,
-      Provider<Label> labelProvider, @Named("fileUpload") boolean showFileUpload) {
-    this.service = service;
-    this.labelProvider = labelProvider;
-    this.format = format;
-    this.showFileUpload = showFileUpload;
-    initWidget(uiBinder.createAndBindUi(this));
-    formPanel.setVisible(this.showFileUpload);
-    formPanel.setAction("/compileserver/upload");
-    formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
-    formPanel.setMethod(FormPanel.METHOD_POST);
-    if (showFileUpload) {
-      startStopButton.setEnabled(false);
-    }
-
-    uploadButton.setEnabled(false);
-    bundle.css().ensureInjected();
-  }
-
-  public void start() {
-    loadBenchmarks();
-  }
-
-  @UiHandler("fileUpload")
-  protected void onUpLoadChanged(@SuppressWarnings("unused") ChangeEvent event) {
-    uploadButton.setEnabled(!"".equals(fileUpload.getFilename()));
-  }
-
-  @UiHandler("uploadButton")
-  protected void onUploadButtonClicked(@SuppressWarnings("unused") ClickEvent event) {
-    formPanel.submit();
-  }
-
-  @UiHandler("formPanel")
-  public void onSubmitComplete(@SuppressWarnings("unused") SubmitCompleteEvent event) {
-    Window.alert("File: " + event.getResults());
-    uploadButton.setEnabled(true);
-    startStopButton.setEnabled(true);
-  }
-
-  @UiHandler("formPanel")
-  public void onSubmit(@SuppressWarnings("unused") SubmitEvent event) {
-    uploadButton.setEnabled(false);
-  }
-
-  @UiHandler("startStopButton")
-  protected void onStartButtonPressed(@SuppressWarnings("unused") ClickEvent e) {
-
-    AsyncCallback<Void> callback = new AsyncCallback<Void>() {
-
-      @Override
-      public void onFailure(Throwable caught) {
-        alert("Error while starting/stopping service");
-      }
-
-      @Override
-      public void onSuccess(Void result) {
-        loadBenchmarks();
-      }
-    };
-
-    if (running) {
-      service.stopServer(showFileUpload, callback);
-    } else {
-      service.startServer(showFileUpload, callback);
-    }
-  }
-
-  private void addClickHandler(Label label, final int row, final int column) {
-    handlers.add(label.addClickHandler(new ClickHandler() {
-
-      @Override
-      public void onClick(ClickEvent event) {
-        onGridEntryClicked(row, column);
-      }
-    }));
-    label.addStyleName(bundle.css().clickable());
-  }
-
-  private void loadBenchmarks() {
-    loadingLabel.setVisible(true);
-    errorLabel.setVisible(false);
-    contentContainer.setVisible(false);
-    statusText.setText("");
-    startStopButton.setVisible(false);
-
-    service.loadBenchmarkOverview(showFileUpload, new AsyncCallback<BenchmarkOverviewResponseDTO>() {
-
-        @Override
-      public void onFailure(Throwable caught) {
-        errorLabel.setVisible(true);
-        errorLabel.setText("Can not load benchmarks");
-        loadingLabel.setVisible(false);
-      }
-
-        @Override
-      public void onSuccess(BenchmarkOverviewResponseDTO result) {
-        renderResult(result);
-      }
-    });
-  }
-
-  private void renderResult(BenchmarkOverviewResponseDTO result) {
-    this.result = result;
-    this.running = result.isExecutingBenchmarks();
-
-    removeHandlers();
-
-    loadingLabel.setVisible(false);
-    contentContainer.setVisible(true);
-    startStopButton.setVisible(true);
-    startStopButton.setText(result.isExecutingBenchmarks() ? "Stop executing" : "Start executing");
-    statusText.setText(result.isExecutingBenchmarks() ? "System is running" : "System is idle");
-    grid.clear();
-
-    if (result.isHasLatestRun()) {
-      int columns = result.getRunnerNames().size() + 1;
-      int rows = result.getBenchmarks().size() + 1;
-      grid.resize(rows, columns);
-      renderHeader(result.getRunnerNames());
-      renderEntries();
-    }
-  }
-
-  private void removeHandlers() {
-    for (HandlerRegistration hr : handlers) {
-      hr.removeHandler();
-    }
-    handlers.clear();
-  }
-
-  private void onGridEntryClicked(int row, int column) {
-    if (row == 0) {
-      return;
-    }
-
-    BenchmarkOverviewEntryDTO entry = result.getBenchmarks().get(row - 1);
-
-    String errorMessage = entry.getErrorMessage();
-    if (errorMessage != null) {
-      alert(errorMessage);
-    } else {
-      errorMessage = entry.getBenchmarkRuns().get(column - 1).getErrorMessage();
-      if (errorMessage != null) {
-        alert(errorMessage);
-      }
-    }
-  }
-
-  private void renderEntries() {
-    for (int row = 0; row < result.getBenchmarks().size(); row++) {
-      BenchmarkOverviewEntryDTO entry = result.getBenchmarks().get(row);
-      Label gridEntry = labelProvider.get();
-      gridEntry.setText(entry.getBenchmarkName());
-      grid.setWidget(row + 1, 0, gridEntry);
-      switch (entry.getState()) {
-        case AT_LEAST_ONE_FAILED:
-          gridEntry.addStyleName(bundle.css().statusOneFailed());
-          for (int column = 0; column < entry.getBenchmarkRuns().size(); column++) {
-            BenchmarkRunDTO benchmarkRun = entry.getBenchmarkRuns().get(column);
-            Label gridNumberEntry = labelProvider.get();
-            if (benchmarkRun.getState() == State.DONE) {
-              gridNumberEntry.setText(format.format(benchmarkRun.getRunsPerMinute()));
-            } else {
-              gridNumberEntry.setText("Error");
-              addClickHandler(gridNumberEntry, row + 1, column + 1);
-            }
-            grid.setWidget(row + 1, column + 1, gridNumberEntry);
-          }
-          break;
-        case DONE:
-          gridEntry.addStyleName(bundle.css().statusDone());
-          for (int column = 0; column < entry.getBenchmarkRuns().size(); column++) {
-            BenchmarkRunDTO benchmarkRun = entry.getBenchmarkRuns().get(column);
-            Label gridNumberEntry = labelProvider.get();
-            gridNumberEntry.setText(format.format(benchmarkRun.getRunsPerMinute()));
-            grid.setWidget(row + 1, column + 1, gridNumberEntry);
-          }
-          break;
-        case FATAL_ERROR:
-          gridEntry.addStyleName(bundle.css().statusFatalError());
-          addClickHandler(gridEntry, row + 1, 0);
-          break;
-        case RUNNING:
-          gridEntry.addStyleName(bundle.css().statusRunning());
-          break;
-      }
-    }
-  }
-
-  private void renderHeader(ArrayList<String> runnerNames) {
-    Label nameEntry = labelProvider.get();
-    nameEntry.setText("Benchmark Name");
-    grid.setWidget(0, 0, nameEntry);
-
-    for (int i = 0; i < runnerNames.size(); i++) {
-      Label entry = labelProvider.get();
-      entry.setText(runnerNames.get(i));
-      grid.setWidget(0, i + 1, entry);
-    }
-  }
-
-  // Visible for testing
-  void alert(String message) {
-    Window.alert(message);
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.ui.xml b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.ui.xml
deleted file mode 100644
index 5bafebf..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.ui.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
-    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
-  <g:HTMLPanel>
-    <g:FlowPanel ui:field="contentContainer">
-      <g:Label ui:field="statusText" />
-      <g:Button ui:field="startStopButton"></g:Button>
-      <g:Grid ui:field="grid"></g:Grid>
-    </g:FlowPanel>
-    <g:Label ui:field="errorLabel"/>
-    <g:Label ui:field="loadingLabel">Loading...</g:Label>
-    <g:FormPanel ui:field="formPanel">
-      <g:FlowPanel>
-        <g:FileUpload ui:field="fileUpload" name="file"></g:FileUpload>
-        <g:Button ui:field="uploadButton">Upload SDK</g:Button>
-      </g:FlowPanel>
-    </g:FormPanel>
-  </g:HTMLPanel>
-</ui:UiBinder>
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/benchmarkstatus.css b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/benchmarkstatus.css
deleted file mode 100644
index 15f0fc9..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/benchmarkstatus.css
+++ /dev/null
@@ -1,19 +0,0 @@
-.clickable {
-  cursor: pointer;
-}
-
-.statusFatalError {
-  background-color: red;
-}
-
-.statusOneFailed {
-  background-color: yellow;
-}
-
-.statusDone {
-  background-color: green;
-}
-
-.statusRunning {
-  background-color: yellow;
-}
\ No newline at end of file
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/BenchmarkModule.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/BenchmarkModule.java
deleted file mode 100644
index 5bdd1bd..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/BenchmarkModule.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.guice;
-
-import com.google.gdata.client.spreadsheet.SpreadsheetService;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkCompiler;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkReporter;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkWorker;
-import com.google.gwt.benchmark.compileserver.server.manager.CliInteractor;
-import com.google.gwt.benchmark.compileserver.server.manager.MailReporter.MailHelper;
-import com.google.gwt.benchmark.compileserver.server.manager.MailReporter.MailHelperProdImpl;
-import com.google.gwt.benchmark.compileserver.server.manager.Runner;
-import com.google.gwt.benchmark.compileserver.server.manager.WebDriverRunner;
-import com.google.gwt.benchmark.compileserver.server.runners.settings.MailSettings;
-import com.google.gwt.benchmark.compileserver.server.runners.settings.Settings;
-import com.google.inject.AbstractModule;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Provides;
-import com.google.inject.assistedinject.FactoryModuleBuilder;
-import com.google.inject.name.Named;
-import com.google.inject.name.Names;
-
-import java.io.File;
-import java.net.URL;
-import java.util.UUID;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * The compile server guice module.
- */
-public class BenchmarkModule extends AbstractModule {
-
-  private final Settings settings;
-
-  public BenchmarkModule(Settings settings) {
-    this.settings = settings;
-  }
-
-  @Override
-  protected void configure() {
-    bind(BenchmarkCompiler.class).to(CliInteractor.class);
-    install(new FactoryModuleBuilder().implement(Runner.class, WebDriverRunner.class).build(
-        Runner.Factory.class));
-    install(new FactoryModuleBuilder().build(BenchmarkWorker.Factory.class));
-    install(new FactoryModuleBuilder().build(BenchmarkReporter.Factory.class));
-    bind(MailHelper.class).to(MailHelperProdImpl.class);
-    bind(String.class).annotatedWith(Names.named("randomStringProvider"))
-        .toProvider(RandomStringProvider.class);
-
-    bind(File.class).annotatedWith(Names.named("compilerOutputDir"))
-        .toInstance(settings.getBenchmarkCompileOutputDir());
-    bind(Integer.class).annotatedWith(Names.named("poolSize"))
-        .toInstance(settings.getThreadPoolSize());
-    bind(Integer.class).annotatedWith(Names.named("port"))
-        .toInstance(settings.getServletContainerPort());
-    bind(File.class).annotatedWith(Names.named("benchmarkSourceLocation"))
-        .toInstance(new File(settings.getBenchmarkRootDirectory(), "src/main/java/"));
-    bind(ExecutorService.class).annotatedWith(Names.named("managerPoolSize"))
-        .toProvider(PoolProvider.class);
-    bind(String.class).annotatedWith(Names.named("ip")).toInstance(settings.getIpAddress());
-    bind(String.class).annotatedWith(Names.named("moduleTemplate"))
-        .toInstance(settings.getModuleTemplate());
-    bind(URL.class).annotatedWith(Names.named("hubUrl")).toInstance(settings.getHubUrl());
-    bind(File.class).annotatedWith(Names.named("scriptDirectory"))
-        .toInstance(settings.getScriptsDirectory());
-    bind(Boolean.class).annotatedWith(Names.named("useReporter"))
-        .toInstance(settings.reportResults());
-    bind(File.class).annotatedWith(Names.named("persistenceDir"))
-        .toInstance(settings.getPersistenceDir());
-    bind(File.class).annotatedWith(Names.named("gwtSourceLocation"))
-        .toInstance(settings.getGwtSourceLocation());
-    bind(MailSettings.class).toInstance(settings.getMailSettings());
-    bind(String.class).annotatedWith(Names.named("spreadSheetId")).toInstance(
-        settings.getSpreadSheetId());
-    bind(String.class).annotatedWith(Names.named("client_json_secret")).toInstance(
-        settings.getOauthSecret());
-  }
-
-  @Provides
-  public SpreadsheetService spreadSheetService() {
-    return new SpreadsheetService("benchmark");
-  }
-
-  private static class PoolProvider implements Provider<ExecutorService> {
-
-    private int poolSize;
-
-    @Inject
-    public PoolProvider(@Named("poolSize") int poolSize) {
-      this.poolSize = poolSize;
-    }
-
-    @Override
-    public ExecutorService get() {
-      return Executors.newFixedThreadPool(poolSize);
-    }
-  }
-
-  private static class RandomStringProvider implements Provider<String> {
-    @Override
-    public String get() {
-      return UUID.randomUUID().toString();
-    }
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManager.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManager.java
deleted file mode 100644
index 9137b41..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManager.java
+++ /dev/null
@@ -1,524 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.manager;
-
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkReporter.Factory;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkReporter.ReportProgressHandler;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkRun.Result;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkWorker.ProgressHandler;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.google.inject.name.Named;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-/**
- * BenchmarkManager is the central place for starting and stopping benchmarks.
- *
- * <p>
- * BenchmarkManager interacts with several other objects to execute benchmarks, pull new changes
- * into the local repository, build the SDK and report results or errors.
- */
-@Singleton
-public class BenchmarkManager {
-
-  private class EventLoop implements Runnable {
-    @Override
-    public void run() {
-      runEventLoop();
-    }
-  }
-
-  private class ThreadSafeProgressHandler implements ProgressHandler {
-
-    private BenchmarkRun benchmarkRun;
-
-    public ThreadSafeProgressHandler(BenchmarkRun benchmarkRun) {
-      this.benchmarkRun = benchmarkRun;
-    }
-
-    @Override
-    public void onCompilationFailed(String message) {
-      synchronized (benchmarkRunsByNameLock) {
-        benchmarkRun.setFailedCompile(message);
-      }
-    }
-
-    @Override
-    public void onResult(RunnerConfig config, double result) {
-      synchronized (benchmarkRunsByNameLock) {
-        benchmarkRun.addResult(config, result);
-      }
-    }
-
-    @Override
-    public void failedToRunBenchmark(RunnerConfig config, String errorMessage) {
-      synchronized (benchmarkRunsByNameLock) {
-        benchmarkRun.getResults().get(config).setErrorMessage(errorMessage);
-        benchmarkRun.setFailedToRunOnServer();
-      }
-    }
-
-    @Override
-    public void onHostPageGenerationFailed(String errorMessage) {
-      synchronized (benchmarkRunsByNameLock) {
-        benchmarkRun.setFailedHostPageGenerationFailed(errorMessage);
-      }
-    }
-
-    @Override
-    public void onRunEnded() {
-      synchronized (benchmarkRunsByNameLock) {
-        if (!benchmarkRun.isFailed()) {
-          benchmarkRun.setRunEnded();
-        }
-      }
-      if (workCount.decrementAndGet() == 0) {
-        maybeReportResults(benchmarkRun.getCommitId());
-      }
-    }
-
-    @Override
-    public void onCompileDirCreationFailed() {
-      synchronized (benchmarkRunsByNameLock) {
-        benchmarkRun.setFailedToCreateDirectory();
-      }
-    }
-  }
-
-  private static final Logger logger = Logger.getLogger(BenchmarkManager.class.getName());
-
-  private static final long TICK_INTERVAL = 10 * 1000L;
-
-  private static Map<String, BenchmarkRun> deepClone(Map<String, BenchmarkRun> runMap) {
-    Map<String, BenchmarkRun> map = new HashMap<>();
-    for (Map.Entry<String, BenchmarkRun> entry : runMap.entrySet()) {
-      map.put(entry.getKey(), BenchmarkRun.from(entry.getValue()));
-    }
-    return map;
-  }
-
-  private static Collection<String> getNonSuccessfulRuns(Map<String, BenchmarkRun> results) {
-
-    List<String> list = new ArrayList<>();
-    for (BenchmarkRun benchmarkRun : results.values()) {
-
-      if (benchmarkRun.isFailed()) {
-        for (Entry<RunnerConfig, Result> result : benchmarkRun.getResults().entrySet()) {
-          list.add(benchmarkRun.getModuleName() + " " + result.getKey());
-        }
-      } else {
-        for (Entry<RunnerConfig, Result> result : benchmarkRun.getResults().entrySet()) {
-          if (result.getValue().getState() != Result.State.DONE) {
-            list.add(benchmarkRun.getModuleName() + " " + result.getKey());
-          }
-        }
-      }
-    }
-    return list;
-  }
-
-  private enum Command {
-    EXIT, CHECK_FOR_UPDATES, RUN_BENCHMARKS, SUCCESSFUL_RUN, FAILED_RUN
-  }
-
-  protected enum State {
-    IDLE, RUNNING_BENCHMARKS
-  }
-
-  private BenchmarkFinder benchmarkFinder;
-
-  private Object benchmarkRunsByNameLock = new Object();
-
-  private Map<String, BenchmarkRun> benchmarkRunsByName = new HashMap<>();
-
-  private BenchmarkWorker.Factory benchmarkWorkerFactory;
-
-  private long currentCommitDateMsEpoch;
-
-  protected boolean currentlyRunning = false;
-
-  private BlockingQueue<Command> commands = new LinkedBlockingQueue<>();
-
-  private String currentCommitId;
-
-  private MailReporter errorReporter;
-
-  private Thread eventLoop;
-
-  private String lastSuccessfulCommitId;
-
-  private ExecutorService pool;
-
-  private Provider<ExecutorService> poolProvider;
-
-  private Factory reporterFactory;
-
-  protected CliInteractor cliInteractor;
-
-  protected State state = State.IDLE;
-
-  private Timer timer;
-
-  private Provider<Timer> timerProvider;
-
-  private boolean useReporter;
-
-  private AtomicInteger workCount = new AtomicInteger();
-
-  protected File devJar;
-
-  protected File userJar;
-
-  @Inject
-  public BenchmarkManager(BenchmarkFinder collector,
-      BenchmarkWorker.Factory benchmarkWorkerFactory,
-      @Named("managerPoolSize") Provider<ExecutorService> poolProvider,
-      BenchmarkReporter.Factory reporterFactory,
-      @Named("useReporter") boolean useReporter,
-      CliInteractor commitReader,
-      Provider<Timer> timerProvider,
-      MailReporter errorReporter,
-      @Named("gwtSourceLocation") File gwtSourceLocation) {
-    this.benchmarkFinder = collector;
-    this.benchmarkWorkerFactory = benchmarkWorkerFactory;
-    this.poolProvider = poolProvider;
-    this.reporterFactory = reporterFactory;
-    this.useReporter = useReporter;
-    this.cliInteractor = commitReader;
-    this.errorReporter = errorReporter;
-    this.timerProvider = timerProvider;
-    devJar = new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-dev.jar");
-    userJar = new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-user.jar");
-  }
-
-  public String getLastCommitId() {
-    synchronized (benchmarkRunsByNameLock) {
-      return lastSuccessfulCommitId;
-    }
-  }
-
-  public Map<String, BenchmarkRun> getLatestRun() {
-    synchronized (benchmarkRunsByNameLock) {
-      return getLatestRunSynchronized(benchmarkRunsByName);
-    }
-  }
-
-  public List<RunnerConfig> getAllRunners() {
-    return Arrays.asList(
-        RunnerConfigs.FIREFOX_LINUX,
-        RunnerConfigs.CHROME_LINUX,
-        RunnerConfigs.IE10_WIN,
-        RunnerConfigs.IE11_WIN);
-  }
-
-  public synchronized boolean isRunning() {
-    return currentlyRunning;
-  }
-
-  public synchronized void start() {
-
-    if (isRunning()) {
-      throw new IllegalStateException();
-    }
-    commands.clear();
-    eventLoop = new Thread(new EventLoop());
-    eventLoop.start();
-
-    timer = timerProvider.get();
-    timer.scheduleAtFixedRate(new TimerTask() {
-
-      @Override
-      public void run() {
-        commands.add(Command.CHECK_FOR_UPDATES);
-      }
-    }, TICK_INTERVAL, TICK_INTERVAL);
-
-    currentlyRunning = true;
-  }
-
-  public synchronized void stop() {
-    if (!isRunning()) {
-      throw new IllegalStateException();
-    }
-
-    commands.add(Command.EXIT);
-    try {
-      eventLoop.join();
-    } catch (InterruptedException e) {
-      // Our framework does not make use of thread.interrupt() so this must mean the JVM is trying
-      // to gracefully shut down in response to an external signal. Let it happen.
-    }
-
-    timer.cancel();
-    commands.clear();
-
-    currentlyRunning = false;
-  }
-
-  protected void addBenchmarkRun(BenchmarkRun br) {
-    synchronized (benchmarkRunsByNameLock) {
-      addBenchmarkRunSynchronized(benchmarkRunsByName, br);
-    }
-  }
-
-  private BenchmarkRun createBenchmarkRunForModule(String moduleName, String commitId,
-      long currentCommitDateMsEpoch) {
-    BenchmarkRun br = new BenchmarkRun(moduleName, commitId, currentCommitDateMsEpoch);
-    for (RunnerConfig config : getAllRunners()) {
-      br.addRunner(config);
-    }
-    return br;
-  }
-
-  protected void maybeReportResults(String commitId) {
-    Map<String, BenchmarkRun> results;
-    synchronized (benchmarkRunsByNameLock) {
-      results = deepClone(benchmarkRunsByName);
-    }
-
-    Collection<String> runs = getNonSuccessfulRuns(results);
-    if (!runs.isEmpty()) {
-      StringBuilder builder = new StringBuilder();
-      builder.append("Failed Benchmarks: \n");
-      for (String errorModule : runs) {
-        builder.append(String.format("%s \n", errorModule));
-      }
-      logger.severe(
-          String.format("Benchmarks failed executing - stopping system\n%s", builder.toString()));
-      reportError("Benchmarks failed executing - stopping system");
-      stop();
-      return;
-    }
-
-    if (!useReporter) {
-      commands.add(Command.SUCCESSFUL_RUN);
-      return;
-    }
-
-    ReportProgressHandler p = new ReportProgressHandler() {
-
-      @Override
-      public void onPermanentFailure() {
-        reportError("Reporter failed to report results, shutting down the system");
-        stop();
-      }
-
-      @Override
-      public void onCommitReported() {
-        commands.add(Command.SUCCESSFUL_RUN);
-      }
-    };
-    new Thread(reporterFactory.create(results, commitId, p)).start();
-  }
-
-  private Command getNextCommand() {
-    try {
-      return commands.take();
-    } catch (InterruptedException e) {
-      // Our framework does not make use of thread.interrupt() so this must mean the JVM is trying
-      // to gracefully shut down in response to an external signal. Let it happen.
-      return Command.EXIT;
-    }
-  }
-
-  protected void runEventLoop() {
-    state = State.IDLE;
-    // check out last successful commit
-    try {
-      logger.info("Getting last commit");
-      setLastCommit(cliInteractor.getLastCommitId());
-      currentCommitId = getLastCommitId();
-      logger.info(String.format("Last commit was %s", currentCommitId));
-      currentCommitDateMsEpoch = cliInteractor.getDateForCommitInMsEpoch(currentCommitId);
-      logger.info("Checking out last commit");
-      cliInteractor.checkout(getLastCommitId());
-
-    } catch (BenchmarkManagerException e) {
-      logger.log(Level.WARNING, "Can not checkout commit - shutting down", e);
-      reportError("Can not update git repo");
-      new Thread(new Runnable() {
-
-        @Override
-        public void run() {
-          stop();
-        }
-      }).start();
-      return;
-    }
-
-    while (true) {
-
-      Command command = getNextCommand();
-      switch (command) {
-        case CHECK_FOR_UPDATES:
-          // do not check for updates if we are already running a benchmark
-          if (state == State.RUNNING_BENCHMARKS) {
-            continue;
-          }
-
-          boolean hasUpdates = false;
-          try {
-            cliInteractor.maybeCheckoutNextCommit(getLastCommitId());
-            String commitId = cliInteractor.getCurrentCommitId();
-            hasUpdates = !currentCommitId.equals(commitId);
-            currentCommitId = commitId;
-
-          } catch (BenchmarkManagerException e) {
-            logger.log(Level.WARNING, "Can not update repository", e);
-            reportError("Can not update git repo");
-            break;
-          }
-
-          try {
-            if (hasUpdates) {
-              logger.info(String.format("found a new commit %s", currentCommitId));
-
-              logger.info("Getting its commit date");
-              currentCommitDateMsEpoch = cliInteractor.getDateForCommitInMsEpoch(currentCommitId);
-
-              logger.info("Building SDK");
-              cliInteractor.buildSDK();
-              logger.info("Starting benchmark runners");
-              startBenchmarkingAllForCommit(currentCommitId, currentCommitDateMsEpoch);
-              state = State.RUNNING_BENCHMARKS;
-            }
-
-          } catch (BenchmarkManagerException e) {
-            logger.log(Level.WARNING, "Can not build SDK", e);
-            reportError("Can not build SDK");
-            break;
-          }
-          break;
-        case EXIT:
-          if (state == State.RUNNING_BENCHMARKS) {
-            try {
-              pool.shutdown();
-              pool.awaitTermination(30, TimeUnit.SECONDS);
-            } catch (InterruptedException e) {
-            }
-          }
-          // end this thread
-          commands.clear();
-          return;
-
-        case RUN_BENCHMARKS:
-          if (state == State.RUNNING_BENCHMARKS) {
-            logger.warning(
-                "asked to run benchmarks, but we are already running them, should not happen");
-            continue;
-          }
-          state = State.RUNNING_BENCHMARKS;
-          startBenchmarkingAllForCommit(currentCommitId, currentCommitDateMsEpoch);
-          break;
-
-        case SUCCESSFUL_RUN:
-          try {
-            cliInteractor.storeCommitId(currentCommitId);
-            setLastCommit(currentCommitId);
-            state = State.IDLE;
-          } catch (BenchmarkManagerException e) {
-            logger.log(Level.WARNING, "Can not store last commitId", e);
-            reportError("Can not store last commitId - quitting");
-            try {
-              pool.shutdown();
-              pool.awaitTermination(30, TimeUnit.SECONDS);
-            } catch (InterruptedException i) {
-            }
-            return;
-          }
-          break;
-        default:
-          logger.severe("Hit default case");
-          break;
-      }
-    }
-  }
-
-  protected void reportError(String message) {
-    errorReporter.sendEmail(message);
-  }
-
-  private void setLastCommit(String commitId) {
-    synchronized (benchmarkRunsByNameLock) {
-      lastSuccessfulCommitId = commitId;
-    }
-  }
-
-  protected void startBenchmarkingAllForCommit(String commitId, long currentCommitDateMsEpoch) {
-
-    pool = poolProvider.get();
-
-    List<String> benchmarkModuleNames = benchmarkFinder.get();
-
-    for (String benchmarkModuleName : benchmarkModuleNames) {
-
-      // we are currently ignoring D8 benchmarks until we have a V8 runner.
-      if (benchmarkModuleName.endsWith("D8")) {
-        continue;
-      }
-
-      // TODO(dankurka): fix navier and remove this
-      if (benchmarkModuleName.endsWith("NavierStokesBenchmarkGWT"))
-      {
-        continue;
-      }
-
-      BenchmarkRun br = createBenchmarkRunForModule(benchmarkModuleName, commitId,
-          currentCommitDateMsEpoch);
-      addBenchmarkRun(br);
-
-      ProgressHandler progressHandler = new ThreadSafeProgressHandler(br);
-
-      BenchmarkWorker worker = benchmarkWorkerFactory.create(
-          BenchmarkWorkerConfig.from(br, devJar, userJar), progressHandler);
-      workCount.incrementAndGet();
-
-      pool.execute(worker);
-    }
-  }
-
-  // Visible for testings
-  void addBenchmarkRunSynchronized(Map<String, BenchmarkRun> runsByName, BenchmarkRun br) {
-    runsByName.put(br.getModuleName(), br);
-  }
-
-  // Visible for testings
-  Map<String, BenchmarkRun> getLatestRunSynchronized(Map<String, BenchmarkRun> runsByName) {
-    return deepClone(runsByName);
-  }
-
-  // Visible for testings
-  boolean isEventLoopAlive() {
-    return eventLoop.isAlive();
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManagerException.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManagerException.java
deleted file mode 100644
index d38e08b..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManagerException.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.manager;
-
-import java.io.IOException;
-
-/**
- * Exception being thrown by components of the {@link BenchmarkManager}.
- */
-public class BenchmarkManagerException extends Exception {
-
-  public BenchmarkManagerException(String message) {
-    super(message);
-  }
-
-  public BenchmarkManagerException(String message, IOException e) {
-    super(message, e);
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorker.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorker.java
deleted file mode 100644
index e60b35b..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorker.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.manager;
-
-import com.google.inject.Provider;
-import com.google.inject.assistedinject.Assisted;
-import com.google.inject.name.Named;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * BenchmarkWorker compiles a single module, writes the host page and asks a Runner to execute the
- * benchmark handing back results.
- */
-public class BenchmarkWorker implements Runnable {
-
-  public interface Factory {
-    BenchmarkWorker create(BenchmarkWorkerConfig benchmarkWorkerConfig,
-        ProgressHandler progressHandler);
-  }
-
-  public interface ProgressHandler {
-    void onCompilationFailed(String message);
-
-    void onResult(RunnerConfig config, double result);
-
-    void failedToRunBenchmark(RunnerConfig config, String errorMessage);
-
-    void onHostPageGenerationFailed(String errorMessage);
-
-    void onRunEnded();
-
-    void onCompileDirCreationFailed();
-  }
-
-  private final BenchmarkCompiler compiler;
-  private final Runner.Factory runnerProvider;
-  private final BenchmarkWorkerConfig benchmarkData;
-  private final ProgressHandler progressHandler;
-  private final String ip;
-  private final String moduleTemplate;
-  private File compilerOutputDir;
-  private int port;
-  private Provider<String> randomStringProvider;
-
-  @Inject
-  public BenchmarkWorker(BenchmarkCompiler compiler,
-      Runner.Factory runnerProvider,
-      @Named("moduleTemplate") String moduleTemplate,
-      @Assisted BenchmarkWorkerConfig benchmarkData,
-      @Assisted ProgressHandler progressHandler,
-      @Named("ip") String ip,
-      @Named("port") int port,
-      @Named("compilerOutputDir") File compilerOutputDir,
-      @Named("randomStringProvider") Provider<String> randomStringProvider) {
-    this.compiler = compiler;
-    this.runnerProvider = runnerProvider;
-    this.moduleTemplate = moduleTemplate;
-    this.benchmarkData = benchmarkData;
-    this.progressHandler = progressHandler;
-    this.ip = ip;
-    this.port = port;
-    this.compilerOutputDir = compilerOutputDir;
-    this.randomStringProvider = randomStringProvider;
-  }
-
-  @Override
-  public void run() {
-    // create working dir
-    String randomDirName = randomStringProvider.get();
-    File outputDir = new File(compilerOutputDir, randomDirName);
-    if (!outputDir.mkdirs()) {
-      progressHandler.onCompileDirCreationFailed();
-      progressHandler.onRunEnded();
-      return;
-    }
-
-    try {
-      compiler.compile(benchmarkData.getModuleName(), outputDir, benchmarkData.getDevJar(),
-          benchmarkData.getUserJar());
-    } catch (BenchmarkCompilerException e) {
-      cleanupDirectory(outputDir);
-      progressHandler.onCompilationFailed(e.getMessage() + " " + e.getOutput());
-      progressHandler.onRunEnded();
-      return;
-    }
-
-    try {
-      writeHostPage(outputDir, benchmarkData.getModuleName());
-    } catch (IOException e) {
-      cleanupDirectory(outputDir);
-      progressHandler.onHostPageGenerationFailed("Failed to write host Page: " + e.getMessage());
-      progressHandler.onRunEnded();
-      return;
-    }
-
-    List<Runner> runners = new ArrayList<>();
-
-    for (RunnerConfig config : benchmarkData.getRunners()) {
-      String url = getUrl(this.port, randomDirName, benchmarkData.getModuleName());
-      Runner r = runnerProvider.create(config, url);
-      runners.add(r);
-      // TODO currently executing runners in sequence, switch to parallel execution.
-      r.run();
-    }
-    for (Runner runner : runners) {
-      if (runner.isFailed()) {
-        progressHandler.failedToRunBenchmark(runner.getConfig(), runner.getErrorMessage());
-      } else {
-        progressHandler.onResult(runner.getConfig(), runner.getResult());
-      }
-    }
-    cleanupDirectory(outputDir);
-    progressHandler.onRunEnded();
-  }
-
-  private void writeHostPage(File outputDir, String moduleName) throws IOException {
-    String tpl =
-        moduleTemplate.replace("{module_nocache}", moduleName + "/" + moduleName + ".nocache.js");
-    FileOutputStream stream = null;
-    try {
-      stream = new FileOutputStream(new File(outputDir, moduleName + ".html"));
-      IOUtils.write(tpl.getBytes("UTF-8"), stream);
-    } finally {
-      IOUtils.closeQuietly(stream);
-    }
-  }
-
-  private String getUrl(int port, String randomDirName, String module) {
-    String url = "http://{host}:{port}/__bench/{dirName}/{module}.html";
-    url = url.replace("{host}", ip);
-    url = url.replace("{port}", "" + port);
-    url = url.replace("{dirName}", randomDirName);
-    url = url.replace("{module}", module);
-    return url;
-  }
-
-  // Visible for Testing
-  void cleanupDirectory(File outputDir) {
-    FileUtils.deleteQuietly(outputDir);
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/RunnerConfigs.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/RunnerConfigs.java
deleted file mode 100644
index ddb49c1..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/RunnerConfigs.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.manager;
-
-import com.google.gwt.benchmark.compileserver.server.manager.RunnerConfig.Browser;
-import com.google.gwt.benchmark.compileserver.server.manager.RunnerConfig.OS;
-
-/**
- * Global Object containing all available {@link RunnerConfig RunnerConfigs}.
- */
-public final class RunnerConfigs {
-
-  private static class RunnerConfigImpl implements RunnerConfig {
-
-    private Browser browser;
-    private OS os;
-    private String version;
-
-    public RunnerConfigImpl(Browser browser, OS os, String version) {
-      this.browser = browser;
-      this.os = os;
-      this.version = version;
-    }
-
-    @Override
-    public Browser getBrowser() {
-      return browser;
-    }
-
-    @Override
-    public OS getOS() {
-      return os;
-    }
-
-    @Override
-    public String getBrowserVersion() {
-      return version;
-    }
-
-    @Override
-    public String toString() {
-      String id =  os + " " + browser;
-      if (!"".equals(version)) {
-        id += " " + version;
-      }
-      return id;
-    }
-  }
-
-  /** Chrome on linux */
-  public static final RunnerConfig CHROME_LINUX =
-      new RunnerConfigImpl(Browser.CHROME, OS.LINUX, "");
-  /** Firefox on linux */
-  public static final RunnerConfig FIREFOX_LINUX =
-      new RunnerConfigImpl(Browser.FIREFOX, OS.LINUX, "");
-  /** IE11 on windows */
-  public static final RunnerConfig IE11_WIN =
-      new RunnerConfigImpl(Browser.INTERNET_EXPLORER, OS.WINDOWS, RunnerConfig.IE_11_VERSION);
-  /** IE10 on windows */
-  public static final RunnerConfig IE10_WIN =
-      new RunnerConfigImpl(Browser.INTERNET_EXPLORER, OS.WINDOWS, RunnerConfig.IE_10_VERSION);
-
-  private RunnerConfigs() {}
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/SingleRunBenchmarkManager.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/SingleRunBenchmarkManager.java
deleted file mode 100644
index ba68085..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/SingleRunBenchmarkManager.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.manager;
-
-import com.google.inject.Provider;
-import com.google.inject.name.Named;
-
-import org.apache.commons.io.FileUtils;
-
-import java.io.File;
-import java.util.Timer;
-import java.util.concurrent.ExecutorService;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-/**
- * SingleRunBenchmarkManager allows for a single (updloaded) versino of the SDK to be benchmarked.
- */
-public class SingleRunBenchmarkManager extends BenchmarkManager {
-
-  private static final Logger logger = Logger.getLogger(BenchmarkManager.class.getName());
-  private File sdkFolder;
-
-  @Inject
-  public SingleRunBenchmarkManager(BenchmarkFinder collector,
-      BenchmarkWorker.Factory benchmarkWorkerFactory,
-      @Named("managerPoolSize") Provider<ExecutorService> poolProvider,
-      BenchmarkReporter.Factory reporterFactory,
-      @Named("useReporter") boolean useReporter,
-      CliInteractor commitReader,
-      Provider<Timer> timerProvider,
-      MailReporter errorReporter,
-      @Named("gwtSourceLocation") File gwtSourceLocation) {
-    super(collector, benchmarkWorkerFactory, poolProvider, reporterFactory, useReporter,
-        commitReader, timerProvider, errorReporter, gwtSourceLocation);
-  }
-
-  @Override
-  protected void runEventLoop() {
-    logger.info("Starting benchmark runners");
-    startBenchmarkingAllForCommit("no_commit", System.currentTimeMillis());
-  }
-
-  public void setSDKDir(File sdkFolder) {
-    // This is a dirty hack to get rid of files of an old run
-    // but for now this is good enough...
-    if (this.sdkFolder != null) {
-      FileUtils.deleteQuietly(this.sdkFolder);
-    }
-
-    this.sdkFolder = sdkFolder;
-    if (!sdkFolder.isDirectory()) {
-      throw new IllegalArgumentException(
-          "sdkFolder does not point to a folder: " + sdkFolder.getAbsolutePath());
-    }
-
-    devJar = new File(sdkFolder, "gwt-dev.jar");
-    if (!devJar.exists()) {
-      throw new IllegalArgumentException("Can not find dev jar");
-    }
-    userJar = new File(sdkFolder, "gwt-user.jar");
-    if (!userJar.exists()) {
-      throw new IllegalArgumentException("Can not find user jar");
-    }
-  }
-
-  @Override
-  protected void maybeReportResults(String commitId) {
-    currentlyRunning = false;
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/ManagerMode.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/ManagerMode.java
deleted file mode 100644
index a313085..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/ManagerMode.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.runners.settings;
-
-/**
- * The mode in which the system is running in.
- *
- * <p>
- * Note: This is currently not being used since the system currently always runs in server mode, but
- * will be eventually extended to be run locally as well.
- */
-public enum ManagerMode {
-  SERVER, LOCAL
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/Settings.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/Settings.java
deleted file mode 100644
index 607bbb8..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/runners/settings/Settings.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.runners.settings;
-
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import org.apache.commons.io.IOUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.net.URL;
-import java.util.Properties;
-
-/**
- * Setting class containing all the configuration for the benchmarking system.
- */
-public class Settings {
-
-  public static Settings parseSettings(File settingsFile) throws Exception {
-    Settings settings = new Settings();
-    Properties prop = new Properties();
-    FileInputStream stream = null;
-    try {
-      stream = new FileInputStream(settingsFile);
-      // load a properties file from class path, inside static method
-      prop.load(stream);
-
-      // get the property value and print it out
-      settings.benchmarkRootDirectory = new File(prop.getProperty("benchmarksDirectory"));
-
-      settings.moduleTemplate =
-          loadModuleTemplate(prop.getProperty("moduleTemplate"));
-      settings.hubUrl = new URL(prop.getProperty("seleniumHubUrl"));
-      settings.benchmarkCompileOutputDir = new File(prop.getProperty("compileOutputDir"));
-      settings.threadPoolSize = Integer.parseInt(prop.getProperty("threadPoolSize"));
-      settings.servletContainerPort = Integer.parseInt(prop.getProperty("servletContainerPort"));
-      settings.ipAddress = Util.getFirstNonLoopbackAddress().getHostAddress();
-      settings.reportResults = prop.getProperty("reportResuts").equals("true");
-      settings.reporterUrl = prop.getProperty("reporterUrl");
-      settings.reporterSecret = prop.getProperty("reporterSecret");
-      settings.mode =
-          prop.getProperty("mode").equals("server") ? ManagerMode.SERVER : ManagerMode.LOCAL;
-      settings.persistenceDir = new File(prop.getProperty("persistenceDir"));
-      settings.gwtSourceLocation = new File(prop.getProperty("gwtSourceLocation"));
-
-      String mailTo = prop.getProperty("mail.to");
-      String mailFrom = prop.getProperty("mail.from");
-      String mailHost = prop.getProperty("mail.host");
-      String mailUsername = prop.getProperty("mail.username");
-      String mailPassword = prop.getProperty("mail.password");
-
-      settings.mailSettings =
-          new MailSettings(mailFrom, mailTo, mailHost, mailUsername, mailPassword);
-      settings.oauthSecret =
-          Files.toString(new File(prop.getProperty("oauth_secret_file")), Charsets.UTF_8);
-      settings.spreadSheetId = prop.getProperty("spreadSheetId");
-    } finally {
-      IOUtils.closeQuietly(stream);
-    }
-    return settings;
-  }
-
-  private static String loadModuleTemplate(String fileName) throws IOException {
-    FileInputStream inputStream = null;
-
-    try {
-      inputStream = new FileInputStream(new File(fileName));
-      return IOUtils.toString(inputStream, "UTF-8");
-    } finally {
-      IOUtils.closeQuietly(inputStream);
-    }
-  }
-
-  private File benchmarkRootDirectory;
-  private String moduleTemplate;
-  private String ipAddress;
-  private URL hubUrl;
-  private File benchmarkCompileOutputDir;
-  private int threadPoolSize;
-  private boolean reportResults;
-  private String reporterUrl;
-  private String reporterSecret;
-  private File persistenceDir;
-  private ManagerMode mode;
-  private File gwtSourceLocation;
-  private MailSettings mailSettings;
-  private int servletContainerPort;
-  private String oauthSecret;
-  private String spreadSheetId;
-
-  public File getBenchmarkRootDirectory() {
-    return benchmarkRootDirectory;
-  }
-
-  public String getModuleTemplate() {
-    return moduleTemplate;
-  }
-
-  public File getScriptsDirectory() {
-    return new File(getBenchmarkRootDirectory(), "src/main/scripts/");
-  }
-
-  public String getIpAddress() {
-    return ipAddress;
-  }
-
-  public URL getHubUrl() {
-    return hubUrl;
-  }
-
-  public File getBenchmarkCompileOutputDir() {
-    return benchmarkCompileOutputDir;
-  }
-
-  public int getThreadPoolSize() {
-    return threadPoolSize;
-  }
-
-  public boolean reportResults() {
-    return reportResults;
-  }
-
-  public String getReporterUrl() {
-    return reporterUrl;
-  }
-
-  public String getReporterSecret() {
-    return reporterSecret;
-  }
-
-  public ManagerMode getMode() {
-    return mode;
-  }
-
-  public File getPersistenceDir() {
-    return persistenceDir;
-  }
-
-  public File getGwtSourceLocation() {
-    return gwtSourceLocation;
-  }
-
-  public MailSettings getMailSettings() {
-    return mailSettings;
-  }
-
-  public int getServletContainerPort() {
-    return servletContainerPort;
-  }
-
-  public String getOauthSecret() {
-    return oauthSecret;
-  }
-
-  public String getSpreadSheetId() {
-    return spreadSheetId;
-  }
-
-  private Settings() {}
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/BenchmarkServiceImpl.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/BenchmarkServiceImpl.java
deleted file mode 100644
index 3d844e6..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/BenchmarkServiceImpl.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.service;
-
-import com.google.gwt.benchmark.common.shared.service.ServiceException;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkManager;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkRun;
-import com.google.gwt.benchmark.compileserver.server.manager.SingleRunBenchmarkManager;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkRun.Result;
-import com.google.gwt.benchmark.compileserver.server.manager.RunnerConfig;
-import com.google.gwt.benchmark.compileserver.shared.Service;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkOverviewEntryDTO;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkOverviewEntryDTO.BenchmarState;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkOverviewResponseDTO;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkRunDTO;
-import com.google.gwt.user.server.rpc.RemoteServiceServlet;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-/**
- * Implementation for GWT service BenchmarkService.
- */
-public class BenchmarkServiceImpl extends RemoteServiceServlet implements Service {
-
-  private static BenchmarkRunDTO.State convertStatus(BenchmarkRun.Result.State state) {
-    switch (state) {
-      case NOT_RUN:
-        return BenchmarkRunDTO.State.NOT_RUN;
-      case DONE:
-        return BenchmarkRunDTO.State.DONE;
-      default:
-        return BenchmarkRunDTO.State.FAILED_RUN;
-    }
-  }
-
-  private static BenchmarkOverviewEntryDTO.BenchmarState convertStatus(BenchmarkRun.State state) {
-    switch (state) {
-      case COMPILING:
-      case NOT_RUN:
-        return BenchmarState.RUNNING;
-      case DONE:
-        return BenchmarState.DONE;
-      case FAILED_TO_RUN_ON_RUNNER:
-        return BenchmarState.AT_LEAST_ONE_FAILED;
-      default:
-        return BenchmarState.FATAL_ERROR;
-    }
-  }
-
-  private static ArrayList<BenchmarkOverviewEntryDTO> createBenchmarkOverviewEntryDTOs(
-      Map<String, BenchmarkRun> latestRun) {
-    ArrayList<BenchmarkOverviewEntryDTO> list = new ArrayList<BenchmarkOverviewEntryDTO>();
-    for (Entry<String, BenchmarkRun> mapEntry : latestRun.entrySet()) {
-      BenchmarkRun serverBenchmarkRun = mapEntry.getValue();
-
-      BenchmarkOverviewEntryDTO entry = new BenchmarkOverviewEntryDTO();
-      entry.setStatus(convertStatus(serverBenchmarkRun.getState()));
-      entry.setBenchmarkName(mapEntry.getKey());
-      if (serverBenchmarkRun.isFailed()) {
-        entry.setErrorMessage(serverBenchmarkRun.getErrorMessage());
-      }
-
-      ArrayList<BenchmarkRunDTO> benchmarkRuns = new ArrayList<BenchmarkRunDTO>();
-      for (Entry<RunnerConfig, Result> runEntries : serverBenchmarkRun.getResults().entrySet()) {
-        Result result = runEntries.getValue();
-        BenchmarkRunDTO benchmarkRun = new BenchmarkRunDTO();
-        benchmarkRun.setState(convertStatus(result.getState()));
-        benchmarkRun.setErrorMessage(result.getErrorMessage());
-        if (result.getState() == Result.State.DONE) {
-          benchmarkRun.setRunsPerMinute(result.getRunsPerSecond());
-        }
-        benchmarkRuns.add(benchmarkRun);
-      }
-      entry.setBenchmarkRuns(benchmarkRuns);
-      list.add(entry);
-    }
-    return list;
-  }
-
-  private static ArrayList<String> createRunnerDTOs(List<RunnerConfig> allRunners) {
-    ArrayList<String> runnerNames = new ArrayList<>();
-    for (RunnerConfig rc : allRunners) {
-      runnerNames.add(rc.toString());
-    }
-    return runnerNames;
-  }
-
-  private static final Logger logger = Logger.getLogger(BenchmarkServiceImpl.class.getName());
-
-  private BenchmarkManager benchmarkManager;
-
-  private SingleRunBenchmarkManager singleRunBenchmarkManager;
-
-  @Inject
-  public BenchmarkServiceImpl(BenchmarkManager benchmarkManager,
-      SingleRunBenchmarkManager singleRunBenchmarkManager) {
-    this.benchmarkManager = benchmarkManager;
-    this.singleRunBenchmarkManager = singleRunBenchmarkManager;
-  }
-
-  @Override
-  public BenchmarkOverviewResponseDTO loadBenchmarkOverview(boolean single) throws ServiceException {
-
-    BenchmarkManager manager = single ? singleRunBenchmarkManager : benchmarkManager;
-
-    try {
-      BenchmarkOverviewResponseDTO response = new BenchmarkOverviewResponseDTO();
-      response.setRunnerNames(createRunnerDTOs(manager.getAllRunners()));
-
-      Map<String, BenchmarkRun> latestRun = manager.getLatestRun();
-      response.setExecutingBenchmarks(manager.isRunning());
-
-      if (latestRun == null || latestRun.isEmpty()) {
-        response.setHasLatestRun(false);
-        // early exit since we do not have any current benchmark to copy
-        return response;
-      }
-
-      response.setHasLatestRun(true);
-      response.setBenchmarks(createBenchmarkOverviewEntryDTOs(latestRun));
-      return response;
-
-    } catch (Exception e) {
-      logger.log(Level.WARNING, "Error while executing service call", e);
-      if (e instanceof ServiceException) {
-        throw (ServiceException) e;
-      }
-      throw new ServiceException("Error while executing your request");
-    }
-  }
-
-  @Override
-  public void startServer(boolean single) throws ServiceException {
-    try {
-      if (single) {
-        singleRunBenchmarkManager.setSDKDir(FileUploadServlet.getSdkFolder());
-        singleRunBenchmarkManager.start();
-      } else {
-        benchmarkManager.start();
-      }
-    } catch (Exception e) {
-      logger.log(Level.WARNING, "failed to start server", e);
-      throw new ServiceException("Failed to start server");
-    }
-  }
-
-  @Override
-  public void stopServer(boolean single) throws ServiceException {
-    try {
-      if (single) {
-        singleRunBenchmarkManager.stop();
-      } else {
-        benchmarkManager.stop();
-      }
-    } catch (Exception e) {
-      logger.log(Level.WARNING, "failed to start server", e);
-      throw new ServiceException("Failed to start server");
-    }
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/FileUploadServlet.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/FileUploadServlet.java
deleted file mode 100644
index 8399aaf..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/FileUploadServlet.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.google.gwt.benchmark.compileserver.server.service;
-
-import com.google.inject.Provider;
-
-import net.lingala.zip4j.core.ZipFile;
-import net.lingala.zip4j.exception.ZipException;
-
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileUploadException;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
-import org.apache.commons.fileupload.servlet.ServletFileUpload;
-import org.apache.commons.io.IOUtils;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * Servlet for SDK uploads.
- */
-public class FileUploadServlet extends HttpServlet {
-
-  private final Provider<String> randomStringProvider;
-
-  private static File sdkFolder;
-
-  public static File getSdkFolder() {
-    return sdkFolder;
-  }
-
-  @Inject
-  public FileUploadServlet(@Named("randomStringProvider") Provider<String> randomStringProvider) {
-    this.randomStringProvider = randomStringProvider;
-  }
-
-  @Override
-  protected void doPost(HttpServletRequest request, HttpServletResponse response)
-      throws ServletException, IOException {
-    try {
-      List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
-      for (FileItem item : items) {
-        if (!item.isFormField()) {
-          InputStream fileContent = item.getInputStream();
-          String randomString = randomStringProvider.get();
-          File tempFile = File.createTempFile(randomString + "-gwt-sdk", ".zip");
-          FileOutputStream tempOutputStream = new FileOutputStream(tempFile);
-          File folder = new File(randomString + "-gwt-sdk/");
-          folder.mkdirs();
-
-          try {
-            IOUtils.copy(fileContent, tempOutputStream);
-            tempOutputStream.close();
-
-            ZipFile zipFile = new ZipFile(tempFile.getAbsolutePath());
-            zipFile.extractAll(folder.getAbsolutePath());
-          } catch (ZipException | IOException e ) {
-           throw new ServletException(e);
-          } finally {
-            tempFile.delete();
-          }
-
-          sdkFolder = new File(folder, "gwt-0.0.0");
-          response.getWriter().write(folder.getAbsolutePath());
-        }
-      }
-    } catch (FileUploadException e) {
-      throw new ServletException("Upload failed.", e);
-    }
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/Service.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/Service.java
deleted file mode 100644
index da7402e..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/Service.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.shared;
-
-import com.google.gwt.benchmark.common.shared.service.ServiceException;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkOverviewResponseDTO;
-import com.google.gwt.user.client.rpc.RemoteService;
-import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
-
-/**
- * Service interface implemented server side.
- */
-@RemoteServiceRelativePath("data/service")
-public interface Service extends RemoteService {
-
-  BenchmarkOverviewResponseDTO loadBenchmarkOverview(boolean single) throws ServiceException;
-
-  void startServer(boolean single) throws ServiceException;
-
-  void stopServer(boolean single) throws ServiceException;
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceAsync.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceAsync.java
deleted file mode 100644
index a7cec5f..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceAsync.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.shared;
-
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkOverviewResponseDTO;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-
-/**
- * Service interface to be injected into every object that needs data from the server.
- */
-public interface ServiceAsync {
-  void loadBenchmarkOverview(boolean single, AsyncCallback<BenchmarkOverviewResponseDTO> callback);
-
-  void startServer(boolean single, AsyncCallback<Void> callback);
-
-  void stopServer(boolean single, AsyncCallback<Void> callback);
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkOverviewEntryDTO.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkOverviewEntryDTO.java
deleted file mode 100644
index 77030cd..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkOverviewEntryDTO.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.shared.dto;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-
-/**
- * Contains data for a specific benchmark.
- */
-public class BenchmarkOverviewEntryDTO implements Serializable {
-
-  public enum BenchmarState {
-    FATAL_ERROR, RUNNING, DONE, AT_LEAST_ONE_FAILED
-  }
-
-  private String benchmarkName;
-  private ArrayList<BenchmarkRunDTO> benchmarkRuns;
-  private BenchmarState state;
-  private String errorMessage;
-
-  public BenchmarkOverviewEntryDTO() {
-  }
-
-  public String getBenchmarkName() {
-    return benchmarkName;
-  }
-
-  public void setBenchmarkName(String benchmarkName) {
-    this.benchmarkName = benchmarkName;
-  }
-
-  public ArrayList<BenchmarkRunDTO> getBenchmarkRuns() {
-    return benchmarkRuns;
-  }
-
-  public void setBenchmarkRuns(ArrayList<BenchmarkRunDTO> benchmarkRuns) {
-    this.benchmarkRuns = benchmarkRuns;
-  }
-
-  public void setStatus(BenchmarState state) {
-    this.state = state;
-  }
-
-  public BenchmarState getState() {
-    return state;
-  }
-
-  public String getErrorMessage() {
-    return errorMessage;
-  }
-
-  public void setErrorMessage(String errorMessage) {
-    this.errorMessage = errorMessage;
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkOverviewResponseDTO.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkOverviewResponseDTO.java
deleted file mode 100644
index d03d2d1..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkOverviewResponseDTO.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.shared.dto;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-
-/**
- * Contains all information of the current run being executed on the compile server.
- */
-public class BenchmarkOverviewResponseDTO implements Serializable {
-
-  private ArrayList<BenchmarkOverviewEntryDTO> benchmarks;
-  private ArrayList<String> runnerNames;
-  private boolean executingBenchmarks;
-  private boolean hasLatestRun;
-
-  public BenchmarkOverviewResponseDTO() {
-  }
-
-  public ArrayList<BenchmarkOverviewEntryDTO> getBenchmarks() {
-    return benchmarks;
-  }
-
-  public void setBenchmarks(ArrayList<BenchmarkOverviewEntryDTO> benchmarks) {
-    this.benchmarks = benchmarks;
-  }
-
-  public ArrayList<String> getRunnerNames() {
-    return runnerNames;
-  }
-
-  public void setRunnerNames(ArrayList<String> runnerNames) {
-    this.runnerNames = runnerNames;
-  }
-
-  public void setExecutingBenchmarks(boolean executingBenchmarks) {
-    this.executingBenchmarks = executingBenchmarks;
-  }
-
-  public boolean isExecutingBenchmarks() {
-    return executingBenchmarks;
-  }
-
-  public void setHasLatestRun(boolean hasLatestRun) {
-    this.hasLatestRun = hasLatestRun;
-  }
-
-  public boolean isHasLatestRun() {
-    return hasLatestRun;
-  }
-}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkRunDTO.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkRunDTO.java
deleted file mode 100644
index d54ac07..0000000
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkRunDTO.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.shared.dto;
-
-import java.io.Serializable;
-
-/**
- * BenchmarkRunDTO represents one exact execution on a specific Runner on the client
- * side. For all the data of one run take a look at {@link BenchmarkOverviewResponseDTO}.
- */
-public class BenchmarkRunDTO implements Serializable {
-
-  public enum State {
-    NOT_RUN, FAILED_RUN, DONE
-  }
-
-  private double runsPerMinute;
-  private State state;
-  private String errorMessage;
-
-  public double getRunsPerMinute() {
-    return runsPerMinute;
-  }
-
-  public void setRunsPerMinute(double runsPerMinute) {
-    this.runsPerMinute = runsPerMinute;
-  }
-
-  public void setState(State state) {
-    this.state = state;
-  }
-
-  public State getState() {
-    return state;
-  }
-
-  public String getErrorMessage() {
-    return errorMessage;
-  }
-
-  public void setErrorMessage(String errorMessage) {
-    this.errorMessage = errorMessage;
-  }
-}
diff --git a/compileserver/src/main/webapp/index.html b/compileserver/src/main/webapp/index.html
deleted file mode 100644
index 82f0f21..0000000
--- a/compileserver/src/main/webapp/index.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
-    <title>CompileServer</title>
-    <script type="text/javascript" src="compileserver/compileserver.nocache.js"></script>
-  </head>
-  <body>
-  </body>
-</html>
diff --git a/compileserver/src/main/webapp/single.html b/compileserver/src/main/webapp/single.html
deleted file mode 100644
index 82f0f21..0000000
--- a/compileserver/src/main/webapp/single.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
-    <title>CompileServer</title>
-    <script type="text/javascript" src="compileserver/compileserver.nocache.js"></script>
-  </head>
-  <body>
-  </body>
-</html>
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusCompositeTest.java b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusCompositeTest.java
deleted file mode 100644
index ea3ef78..0000000
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusCompositeTest.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.client.status;
-
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import com.google.gwt.benchmark.compileserver.shared.ServiceAsync;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkOverviewEntryDTO;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkOverviewEntryDTO.BenchmarState;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkOverviewResponseDTO;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkRunDTO;
-import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkRunDTO.State;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.i18n.client.NumberFormat;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwtmockito.GwtMockitoTestRunner;
-import com.google.inject.Provider;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Test for {@link BenchmarkStatusComposite}.
- */
-@RunWith(GwtMockitoTestRunner.class)
-public class BenchmarkStatusCompositeTest {
-
-  private BenchmarkStatusComposite composite;
-  private List<String> messages = new ArrayList<>();
-
-  @Mock private ServiceAsync service;
-  @Mock private NumberFormat numberFormat;
-  @Mock private Provider<Label> labelProvider;
-
-  @Captor private ArgumentCaptor<AsyncCallback<BenchmarkOverviewResponseDTO>> asyncCaptor;
-  @Captor private ArgumentCaptor<ClickHandler> clickHandler1;
-  @Captor private ArgumentCaptor<ClickHandler> clickHandler2;
-
-  @Before
-  public void setup() {
-    messages.clear();
-    composite = new BenchmarkStatusComposite(service, numberFormat, labelProvider, false) {
-      @Override
-      void alert(String message) {
-        messages.add(message);
-      }
-    };
-
-    when(numberFormat.format(22)).thenReturn("22");
-    when(numberFormat.format(33)).thenReturn("33");
-  }
-
-  @Test
-  public void testPresenterAddsViewAndInitializedView() {
-    composite.start();
-    verify(composite.loadingLabel).setVisible(true);
-    verify(composite.errorLabel).setVisible(false);
-    verify(composite.contentContainer).setVisible(false);
-    verify(composite.statusText).setText("");
-    verify(composite.errorLabel).setVisible(false);
-  }
-
-  @Test
-  public void testRenderDataWhileServerIsNotRunning() {
-    composite.start();
-
-    verify(composite.loadingLabel).setVisible(true);
-    verify(composite.errorLabel).setVisible(false);
-    verify(composite.contentContainer).setVisible(false);
-    verify(composite.statusText).setText("");
-    verify(composite.startStopButton).setVisible(false);
-
-    verify(service).loadBenchmarkOverview(eq(false), asyncCaptor.capture());
-
-    AsyncCallback<BenchmarkOverviewResponseDTO> asyncCallback = asyncCaptor.getValue();
-
-    BenchmarkOverviewResponseDTO response = new BenchmarkOverviewResponseDTO();
-    response.setHasLatestRun(false);
-
-    asyncCallback.onSuccess(response);
-
-    verify(composite.loadingLabel).setVisible(false);
-    verify(composite.contentContainer).setVisible(true);
-    verify(composite.startStopButton).setVisible(true);
-    verify(composite.startStopButton).setText("Start executing");
-    verify(composite.statusText).setText("System is idle");
-    verify(composite.grid).clear();
-
-    verifyNoMoreInteractions(composite.contentContainer, composite.errorLabel,
-        composite.grid, composite.loadingLabel, composite.startStopButton, composite.statusText);
-
-  }
-
-  @Test
-  public void testRenderDataWhileBenchmarksRunning() {
-
-    composite.start();
-
-    verify(composite.loadingLabel).setVisible(true);
-    verify(composite.errorLabel).setVisible(false);
-    verify(composite.contentContainer).setVisible(false);
-    verify(composite.statusText).setText("");
-    verify(composite.startStopButton).setVisible(false);
-
-    verify(service).loadBenchmarkOverview(eq(false), asyncCaptor.capture());
-
-    AsyncCallback<BenchmarkOverviewResponseDTO> asyncCallback = asyncCaptor.getValue();
-
-    BenchmarkOverviewResponseDTO response = new BenchmarkOverviewResponseDTO();
-    response.setHasLatestRun(true);
-
-    // first entry
-    BenchmarkOverviewEntryDTO benchmarkOverviewEntry = new BenchmarkOverviewEntryDTO();
-    benchmarkOverviewEntry.setBenchmarkName("benchmark1");
-    benchmarkOverviewEntry.setStatus(BenchmarState.DONE);
-    benchmarkOverviewEntry.setErrorMessage(null);
-    BenchmarkRunDTO benchmarkRunDTO = new BenchmarkRunDTO();
-    benchmarkRunDTO.setErrorMessage(null);
-    benchmarkRunDTO.setRunsPerMinute(22);
-    benchmarkRunDTO.setState(State.DONE);
-    BenchmarkRunDTO benchmarkRunDTO1 = new BenchmarkRunDTO();
-    benchmarkRunDTO1.setErrorMessage(null);
-    benchmarkRunDTO1.setRunsPerMinute(33);
-    benchmarkRunDTO1.setState(State.DONE);
-    benchmarkOverviewEntry.setBenchmarkRuns(
-        new ArrayList<>(Arrays.asList(benchmarkRunDTO, benchmarkRunDTO1)));
-
-    // second entry
-    BenchmarkOverviewEntryDTO benchmarkOverviewEntry1 = new BenchmarkOverviewEntryDTO();
-    benchmarkOverviewEntry1.setBenchmarkName("benchmark2");
-    benchmarkOverviewEntry1.setStatus(BenchmarState.FATAL_ERROR);
-    benchmarkOverviewEntry1.setErrorMessage("benchmark2 error message");
-    benchmarkRunDTO = new BenchmarkRunDTO();
-    benchmarkRunDTO1 = new BenchmarkRunDTO();
-    benchmarkOverviewEntry1.setBenchmarkRuns(
-        new ArrayList<>(Arrays.asList(benchmarkRunDTO, benchmarkRunDTO1)));
-
-    // third entry
-    BenchmarkOverviewEntryDTO benchmarkOverviewEntry2 = new BenchmarkOverviewEntryDTO();
-    benchmarkOverviewEntry2.setBenchmarkName("benchmark3");
-    benchmarkOverviewEntry2.setStatus(BenchmarState.RUNNING);
-    benchmarkOverviewEntry2.setErrorMessage(null);
-    benchmarkRunDTO = new BenchmarkRunDTO();
-    benchmarkRunDTO1 = new BenchmarkRunDTO();
-    benchmarkOverviewEntry1.setBenchmarkRuns(
-        new ArrayList<>(Arrays.asList(benchmarkRunDTO, benchmarkRunDTO1)));
-
-    // fourth entry
-    BenchmarkOverviewEntryDTO benchmarkOverviewEntry3 = new BenchmarkOverviewEntryDTO();
-    benchmarkOverviewEntry3.setBenchmarkName("benchmark4");
-    benchmarkOverviewEntry3.setStatus(BenchmarState.AT_LEAST_ONE_FAILED);
-    benchmarkOverviewEntry3.setErrorMessage(null);
-    benchmarkRunDTO = new BenchmarkRunDTO();
-    benchmarkRunDTO.setRunsPerMinute(22);
-    benchmarkRunDTO.setState(State.DONE);
-    benchmarkRunDTO1 = new BenchmarkRunDTO();
-    benchmarkRunDTO1.setErrorMessage("error message b4 r2");
-    benchmarkRunDTO1.setState(State.FAILED_RUN);
-    benchmarkOverviewEntry3.setBenchmarkRuns(
-        new ArrayList<>(Arrays.asList(benchmarkRunDTO, benchmarkRunDTO1)));
-
-    response.setBenchmarks(new ArrayList<>(Arrays.asList(benchmarkOverviewEntry,
-        benchmarkOverviewEntry1, benchmarkOverviewEntry2, benchmarkOverviewEntry3)));
-    response.setRunnerNames(new ArrayList<>(Arrays.asList("chrome_linux", "firefox_linux")));
-
-    // header
-    Label gridEntry_0_0 = mock(Label.class);
-    Label gridEntry_0_1 = mock(Label.class);
-    Label gridEntry_0_2 = mock(Label.class);
-
-    // first entry
-    Label gridEntry_1_0 = mock(Label.class);
-    Label gridEntry_1_1 = mock(Label.class);
-    Label gridEntry_1_2 = mock(Label.class);
-
-    // second entry
-    Label gridEntry_2_0 = mock(Label.class);
-
-    // third entry
-    Label gridEntry_3_0 = mock(Label.class);
-
-    // fourth entry
-    Label gridEntry_4_0 = mock(Label.class);
-    Label gridEntry_4_1 = mock(Label.class);
-    Label gridEntry_4_2 = mock(Label.class);
-
-    when(labelProvider.get()).thenReturn(gridEntry_0_0, gridEntry_0_1, gridEntry_0_2,
-        gridEntry_1_0, gridEntry_1_1, gridEntry_1_2, gridEntry_2_0, gridEntry_3_0, gridEntry_4_0,
-        gridEntry_4_1, gridEntry_4_2);
-
-    asyncCallback.onSuccess(response);
-
-    verify(composite.grid).clear();
-    verify(composite.grid).resize(5, 3);
-
-    verify(composite.loadingLabel).setVisible(true);
-    verify(composite.contentContainer).setVisible(false);
-    verify(composite.statusText).setText("System is idle");
-    verify(composite.startStopButton).setVisible(true);
-    verify(composite.startStopButton).setText("Start executing");
-
-    // header
-    verify(gridEntry_0_0).setText("Benchmark Name");
-    verify(composite.grid).setWidget(0, 0, gridEntry_0_0);
-    verify(gridEntry_0_1).setText("chrome_linux");
-    verify(composite.grid).setWidget(0, 1, gridEntry_0_1);
-    verify(gridEntry_0_2).setText("firefox_linux");
-    verify(composite.grid).setWidget(0, 2, gridEntry_0_2);
-    verifyNoMoreInteractions(gridEntry_0_0, gridEntry_0_1, gridEntry_0_2);
-
-    // first entry
-    verify(gridEntry_1_0).setText("benchmark1");
-    verify(gridEntry_1_0).addStyleName(BenchmarkStatusComposite.bundle.css().statusDone());
-    verify(composite.grid).setWidget(1, 0, gridEntry_1_0);
-    verify(gridEntry_1_1).setText("22");
-    verify(composite.grid).setWidget(1, 1, gridEntry_1_1);
-    verify(gridEntry_1_2).setText("33");
-    verify(composite.grid).setWidget(1, 2, gridEntry_1_2);
-    verifyNoMoreInteractions(gridEntry_1_0, gridEntry_1_1, gridEntry_1_2);
-
-    // second entry
-    verify(gridEntry_2_0).setText("benchmark2");
-    verify(gridEntry_2_0).addStyleName(
-        BenchmarkStatusComposite.bundle.css().statusFatalError());
-    verify(gridEntry_2_0).addStyleName(BenchmarkStatusComposite.bundle.css().clickable());
-    verify(gridEntry_2_0).addClickHandler(clickHandler1.capture());
-    verify(composite.grid).setWidget(2, 0, gridEntry_2_0);
-    verifyNoMoreInteractions(gridEntry_2_0);
-
-    // third entry
-    verify(gridEntry_3_0).setText("benchmark3");
-    verify(gridEntry_3_0).addStyleName(
-        BenchmarkStatusComposite.bundle.css().statusRunning());
-    verify(composite.grid).setWidget(3, 0, gridEntry_3_0);
-    verifyNoMoreInteractions(gridEntry_3_0);
-
-    // fourth entry
-    verify(gridEntry_4_0).setText("benchmark4");
-    verify(gridEntry_4_0).addStyleName(
-        BenchmarkStatusComposite.bundle.css().statusOneFailed());
-    verify(composite.grid).setWidget(4, 0, gridEntry_4_0);
-    verify(gridEntry_4_1).setText("22");
-    verify(composite.grid).setWidget(4, 1, gridEntry_4_1);
-    verify(gridEntry_4_2).setText("Error");
-    verify(gridEntry_4_2).addStyleName(BenchmarkStatusComposite.bundle.css().clickable());
-    verify(gridEntry_4_2).addClickHandler(clickHandler2.capture());
-    verify(composite.grid).setWidget(4, 2, gridEntry_4_2);
-
-    verifyNoMoreInteractions(gridEntry_4_0, gridEntry_4_1, gridEntry_4_2);
-
-
-    // Click the first error
-    clickHandler1.getValue().onClick(null);
-    // Click the second error
-    clickHandler2.getValue().onClick(null);
-
-    // we should have alerted two messages
-    Assert.assertEquals(2, messages.size());
-    Assert.assertEquals("benchmark2 error message", messages.get(0));
-    Assert.assertEquals("error message b4 r2", messages.get(1));
-  }
-}
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManagerTest.java b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManagerTest.java
deleted file mode 100644
index ddb1c14..0000000
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManagerTest.java
+++ /dev/null
@@ -1,613 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.manager;
-
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkReporter.ReportProgressHandler;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkWorker.Factory;
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkWorker.ProgressHandler;
-import com.google.inject.Provider;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-import org.mockito.verification.VerificationWithTimeout;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.ThreadPoolExecutor;
-
-/**
- * Test for {@link BenchmarkManager}.
- */
-public class BenchmarkManagerTest {
-
-  private interface Condition {
-    boolean condition();
-  }
-
-  private static class BenchmarkManagerWithPublicAdd extends BenchmarkManager {
-    public BenchmarkManagerWithPublicAdd(BenchmarkFinder collector,
-        Factory benchmarkWorkerFactory,
-        Provider<ExecutorService> poolProvider,
-        com.google.gwt.benchmark.compileserver.server.manager.BenchmarkReporter.Factory reporterFactory,
-        boolean useReporter,
-        CliInteractor commitReader,
-        Provider<Timer> timerProvider,
-        MailReporter errorReporter,
-        File gwtSourceLocation) {
-      super(collector,
-          benchmarkWorkerFactory,
-          poolProvider,
-          reporterFactory,
-          useReporter,
-          commitReader,
-          timerProvider,
-          errorReporter,
-          gwtSourceLocation);
-    }
-
-    @Override
-    public void addBenchmarkRun(BenchmarkRun br) {
-      super.addBenchmarkRun(br);
-    }
-  }
-
-  @SuppressWarnings("unchecked")
-  public static <T> T cast(Object a) {
-    return (T) a;
-  }
-
-  private BenchmarkFinder collector;
-  private BenchmarkWorker.Factory benchmarkWorkerFactory;
-  private Provider<ExecutorService> poolProvider;
-  private Provider<Timer> timerProvider;
-  private BenchmarkReporter.Factory reporterFactory;
-  private BenchmarkManager manager;
-  private CliInteractor commitReader;
-  private ThreadPoolExecutor threadPoolExecutor;
-  private BenchmarkWorker benchmarkWorker;
-  private BenchmarkReporter benchmarkReporter;
-  private MailReporter errorReporter;
-  private Timer timer;
-  private File gwtSourceLocation;
-
-  @Before
-  public void setup() {
-    collector = Mockito.mock(BenchmarkFinder.class);
-    benchmarkWorkerFactory = Mockito.mock(BenchmarkWorker.Factory.class);
-    poolProvider = cast(Mockito.mock(Provider.class));
-    reporterFactory = Mockito.mock(BenchmarkReporter.Factory.class);
-    commitReader = Mockito.mock(CliInteractor.class);
-    timerProvider = cast(Mockito.mock(Provider.class));
-    threadPoolExecutor = Mockito.mock(ThreadPoolExecutor.class);
-    benchmarkWorker = Mockito.mock(BenchmarkWorker.class);
-    benchmarkReporter = Mockito.mock(BenchmarkReporter.class);
-    errorReporter = Mockito.mock(MailReporter.class);
-    timer = Mockito.mock(Timer.class);
-    gwtSourceLocation = new File("");
-  }
-
-  @Test
-  public void testSuccessfulRun() throws BenchmarkManagerException, InterruptedException {
-
-    Mockito.when(timerProvider.get()).thenReturn(timer);
-
-    manager = new BenchmarkManager(collector,
-        benchmarkWorkerFactory,
-        poolProvider,
-        reporterFactory,
-        true,
-        commitReader,
-        timerProvider,
-        errorReporter,
-        gwtSourceLocation);
-
-    Mockito.when(commitReader.getLastCommitId()).thenReturn("commit1");
-    Mockito.when(commitReader.getCurrentCommitId()).thenReturn("commit2");
-    Mockito.when(poolProvider.get()).thenReturn(threadPoolExecutor);
-    Mockito.when(collector.get()).thenReturn(Arrays.asList("module1", "module2"));
-
-    ArgumentCaptor<ProgressHandler> progressHandlerCaptor =
-        ArgumentCaptor.forClass(ProgressHandler.class);
-
-    ArgumentCaptor<BenchmarkWorkerConfig> workerConfigCapture =
-        ArgumentCaptor.forClass(BenchmarkWorkerConfig.class);
-
-    Mockito.when(benchmarkWorkerFactory.create(workerConfigCapture.capture(),
-        progressHandlerCaptor.capture())).thenReturn(benchmarkWorker);
-
-    ArgumentCaptor<Map<String, BenchmarkRun>> resultCaptor =
-        cast(ArgumentCaptor.forClass(Map.class));
-
-    ArgumentCaptor<ReportProgressHandler> reportProgressHandlerCaptor =
-        ArgumentCaptor.forClass(ReportProgressHandler.class);
-
-    Mockito.when(reporterFactory.create(resultCaptor.capture(), Mockito.anyString(),
-        reportProgressHandlerCaptor.capture())).thenReturn(benchmarkReporter);
-
-    Assert.assertFalse(manager.isRunning());
-
-    manager.start();
-
-    Assert.assertTrue(manager.isRunning());
-    Assert.assertTrue(manager.isEventLoopAlive());
-
-    ArgumentCaptor<TimerTask> captor = ArgumentCaptor.forClass(TimerTask.class);
-
-    Mockito.verify(timer).scheduleAtFixedRate(captor.capture(), Mockito.anyLong(),
-        Mockito.anyLong());
-
-    TimerTask timerTask = captor.getValue();
-    timerTask.run();
-
-    VerificationWithTimeout timeout = Mockito.timeout(200);
-    Mockito.verify(commitReader, timeout).checkout("commit1");
-    Mockito.verify(commitReader, timeout).maybeCheckoutNextCommit("commit1");
-    Mockito.verify(poolProvider, timeout).get();
-    Mockito.verify(collector, timeout).get();
-    Mockito.verify(threadPoolExecutor, timeout.times(2)).execute(benchmarkWorker);
-
-    List<ProgressHandler> progressHandlers = progressHandlerCaptor.getAllValues();
-    Assert.assertEquals(2, progressHandlers.size());
-    List<BenchmarkWorkerConfig> workerConfigs = workerConfigCapture.getAllValues();
-    Assert.assertEquals(2, workerConfigs.size());
-
-    // TODO right now there is only one runner, needs updating
-    // simulate benchmarks done
-    progressHandlers.get(0).onResult(workerConfigs.get(0).getRunners().get(0), 1);
-    progressHandlers.get(0).onResult(workerConfigs.get(0).getRunners().get(1), 2);
-    progressHandlers.get(0).onResult(workerConfigs.get(0).getRunners().get(2), 3);
-    progressHandlers.get(0).onResult(workerConfigs.get(0).getRunners().get(3), 4);
-    progressHandlers.get(0).onRunEnded();
-
-    progressHandlers.get(1).onResult(workerConfigs.get(0).getRunners().get(0), 5);
-    progressHandlers.get(1).onResult(workerConfigs.get(0).getRunners().get(1), 6);
-    progressHandlers.get(1).onResult(workerConfigs.get(0).getRunners().get(2), 7);
-    progressHandlers.get(1).onResult(workerConfigs.get(0).getRunners().get(3), 8);
-    progressHandlers.get(1).onRunEnded();
-
-    Mockito.verify(benchmarkReporter, timeout).run();
-
-    Map<String, BenchmarkRun> map = resultCaptor.getValue();
-    Assert.assertEquals(2, map.size());
-
-    Assert.assertTrue(map.containsKey("module1"));
-    Assert.assertTrue(map.containsKey("module2"));
-
-    BenchmarkRun benchmarkRun = map.get("module1");
-
-    Assert.assertEquals("commit2", benchmarkRun.getCommitId());
-    Assert.assertEquals("module1", benchmarkRun.getModuleName());
-    Assert.assertEquals(BenchmarkRun.State.DONE, benchmarkRun.getState());
-    Assert.assertEquals(1,
-        benchmarkRun.getResults().get(benchmarkRun.getRunConfigs().get(0)).getRunsPerSecond(),
-        0.0001);
-    Assert.assertEquals(2,
-        benchmarkRun.getResults().get(benchmarkRun.getRunConfigs().get(1)).getRunsPerSecond(),
-        0.0001);
-    Assert.assertEquals(3,
-        benchmarkRun.getResults().get(benchmarkRun.getRunConfigs().get(2)).getRunsPerSecond(),
-        0.0001);
-    Assert.assertEquals(4,
-        benchmarkRun.getResults().get(benchmarkRun.getRunConfigs().get(3)).getRunsPerSecond(),
-        0.0001);
-
-    benchmarkRun = map.get("module2");
-    Assert.assertEquals("commit2", benchmarkRun.getCommitId());
-    Assert.assertEquals("module2", benchmarkRun.getModuleName());
-    Assert.assertEquals(BenchmarkRun.State.DONE, benchmarkRun.getState());
-    Assert.assertEquals(5,
-        benchmarkRun.getResults().get(benchmarkRun.getRunConfigs().get(0)).getRunsPerSecond(),
-        0.0001);
-    Assert.assertEquals(6,
-        benchmarkRun.getResults().get(benchmarkRun.getRunConfigs().get(1)).getRunsPerSecond(),
-        0.0001);
-    Assert.assertEquals(7,
-        benchmarkRun.getResults().get(benchmarkRun.getRunConfigs().get(2)).getRunsPerSecond(),
-        0.0001);
-    Assert.assertEquals(8,
-        benchmarkRun.getResults().get(benchmarkRun.getRunConfigs().get(3)).getRunsPerSecond(),
-        0.0001);
-
-    // tell benchmark manager that we reported successfully
-    ReportProgressHandler progressHandler = reportProgressHandlerCaptor.getValue();
-    progressHandler.onCommitReported();
-
-    // wait for benchmark manager to change to the next commit
-    waitFor(new Condition() {
-      @Override
-      public boolean condition() {
-        return manager.getLastCommitId().equals("commit2");
-      }
-    }, 200);
-
-    Assert.assertTrue(manager.isRunning());
-    Assert.assertTrue(manager.isEventLoopAlive());
-
-    manager.stop();
-
-    Assert.assertFalse(manager.isRunning());
-
-    waitFor(new Condition() {
-      @Override
-      public boolean condition() {
-        return !manager.isEventLoopAlive();
-      }
-    }, 200);
-  }
-
-  @Test
-  public void testEventLoopFailsOnStartupWhileGettingCommitId() throws BenchmarkManagerException,
-      InterruptedException {
-    Mockito.when(timerProvider.get()).thenReturn(timer);
-    manager = new BenchmarkManager(collector,
-        benchmarkWorkerFactory,
-        poolProvider,
-        reporterFactory,
-        true,
-        commitReader,
-        timerProvider,
-        errorReporter,
-        gwtSourceLocation);
-
-    Mockito.when(commitReader.getLastCommitId()).thenThrow(new BenchmarkManagerException(""));
-
-    Assert.assertFalse(manager.isRunning());
-    manager.start();
-    VerificationWithTimeout timeout = Mockito.timeout(200);
-    Mockito.verify(errorReporter, timeout).sendEmail("Can not update git repo");
-    waitFor(new Condition() {
-
-      @Override
-      public boolean condition() {
-        return !manager.isRunning();
-      }
-    }, 200);
-    Assert.assertFalse(manager.isRunning());
-    Assert.assertFalse(manager.isEventLoopAlive());
-  }
-
-  @Test
-  public void testEventLoopFailsOnStartupWhileGettingCommitDate() throws BenchmarkManagerException,
-      InterruptedException {
-    Mockito.when(timerProvider.get()).thenReturn(timer);
-    manager = new BenchmarkManager(collector,
-        benchmarkWorkerFactory,
-        poolProvider,
-        reporterFactory,
-        true,
-        commitReader,
-        timerProvider,
-        errorReporter,
-        gwtSourceLocation);
-
-    Mockito.when(commitReader.getDateForCommitInMsEpoch(Mockito.anyString())).thenThrow(
-        new BenchmarkManagerException(""));
-
-    Assert.assertFalse(manager.isRunning());
-    manager.start();
-    VerificationWithTimeout timeout = Mockito.timeout(200);
-    Mockito.verify(errorReporter, timeout).sendEmail("Can not update git repo");
-    waitFor(new Condition() {
-
-      @Override
-      public boolean condition() {
-        return !manager.isRunning();
-      }
-    }, 200);
-    Assert.assertFalse(manager.isRunning());
-    Assert.assertFalse(manager.isEventLoopAlive());
-  }
-
-  @Test
-  public void testEventLoopFailsOnStartupWhileCheckingOutCurrentCommit()
-      throws BenchmarkManagerException, InterruptedException {
-    Mockito.when(timerProvider.get()).thenReturn(timer);
-    manager = new BenchmarkManager(collector,
-        benchmarkWorkerFactory,
-        poolProvider,
-        reporterFactory,
-        true,
-        commitReader,
-        timerProvider,
-        errorReporter,
-        gwtSourceLocation);
-
-    Mockito.doThrow(new BenchmarkManagerException("")).when(commitReader)
-        .checkout(Mockito.anyString());
-
-    Assert.assertFalse(manager.isRunning());
-    manager.start();
-    VerificationWithTimeout timeout = Mockito.timeout(200);
-    Mockito.verify(errorReporter, timeout).sendEmail("Can not update git repo");
-    waitFor(new Condition() {
-
-      @Override
-      public boolean condition() {
-        return !manager.isRunning();
-      }
-    }, 200);
-    Assert.assertFalse(manager.isRunning());
-    Assert.assertFalse(manager.isEventLoopAlive());
-  }
-
-  @Test
-  public void testFailedToCompileBenchmark() throws BenchmarkManagerException,
-      InterruptedException {
-
-    Mockito.when(timerProvider.get()).thenReturn(timer);
-
-    manager = new BenchmarkManager(collector,
-        benchmarkWorkerFactory,
-        poolProvider,
-        reporterFactory,
-        true,
-        commitReader,
-        timerProvider,
-        errorReporter,
-        gwtSourceLocation);
-
-    Mockito.when(commitReader.getLastCommitId()).thenReturn("commit1");
-    Mockito.when(commitReader.getCurrentCommitId()).thenReturn("commit2");
-    Mockito.when(poolProvider.get()).thenReturn(threadPoolExecutor);
-    Mockito.when(collector.get()).thenReturn(Arrays.asList("module1", "module2"));
-
-    ArgumentCaptor<ProgressHandler> progressHandlerCaptor =
-        ArgumentCaptor.forClass(ProgressHandler.class);
-
-    ArgumentCaptor<BenchmarkWorkerConfig> benchmarkWorkerConfigCaptor =
-        ArgumentCaptor.forClass(BenchmarkWorkerConfig.class);
-
-    Mockito.when(benchmarkWorkerFactory.create(benchmarkWorkerConfigCaptor.capture(),
-        progressHandlerCaptor.capture())).thenReturn(benchmarkWorker);
-
-    ArgumentCaptor<Map<String, BenchmarkRun>> resultCaptor =
-        cast(ArgumentCaptor.forClass(Map.class));
-
-    ArgumentCaptor<ReportProgressHandler> reportProgressHandlerCaptor =
-        ArgumentCaptor.forClass(ReportProgressHandler.class);
-
-    Mockito.when(reporterFactory.create(resultCaptor.capture(), Mockito.anyString(),
-        reportProgressHandlerCaptor.capture())).thenReturn(benchmarkReporter);
-
-    Assert.assertFalse(manager.isRunning());
-
-    manager.start();
-
-    Assert.assertTrue(manager.isRunning());
-    Assert.assertTrue(manager.isEventLoopAlive());
-
-    ArgumentCaptor<TimerTask> captor = ArgumentCaptor.forClass(TimerTask.class);
-
-    Mockito.verify(timer).scheduleAtFixedRate(captor.capture(), Mockito.anyLong(),
-        Mockito.anyLong());
-
-    TimerTask timerTask = captor.getValue();
-    timerTask.run();
-
-    VerificationWithTimeout timeout = Mockito.timeout(200);
-    Mockito.verify(commitReader, timeout).checkout("commit1");
-    Mockito.verify(commitReader, timeout).maybeCheckoutNextCommit("commit1");
-    Mockito.verify(poolProvider, timeout).get();
-    Mockito.verify(collector, timeout).get();
-    Mockito.verify(threadPoolExecutor, timeout.times(2)).execute(benchmarkWorker);
-
-    List<ProgressHandler> progressHandlers = progressHandlerCaptor.getAllValues();
-    Assert.assertEquals(2, progressHandlers.size());
-    List<BenchmarkWorkerConfig> workerConfigs = benchmarkWorkerConfigCaptor.getAllValues();
-    Assert.assertEquals(2, workerConfigs.size());
-
-    // TODO right now there is only one runner, needs updating
-    // simulate benchmarks done
-    progressHandlers.get(0).onResult(workerConfigs.get(0).getRunners().get(0), 1);
-    progressHandlers.get(0).onCompilationFailed("bad module1");
-    progressHandlers.get(0).onRunEnded();
-    progressHandlers.get(1).onResult(workerConfigs.get(0).getRunners().get(0), 2);
-    progressHandlers.get(1).onCompilationFailed("bad module2");
-    progressHandlers.get(1).onRunEnded();
-
-    Mockito.verifyZeroInteractions(benchmarkReporter);
-
-    Map<String, BenchmarkRun> map = manager.getLatestRun();
-    Assert.assertEquals(2, map.size());
-
-    Assert.assertTrue(map.containsKey("module1"));
-    Assert.assertTrue(map.containsKey("module2"));
-
-    BenchmarkRun benchmarkRun = map.get("module1");
-    Assert.assertEquals(BenchmarkRun.State.FAILED_COMPILE, benchmarkRun.getState());
-    benchmarkRun = map.get("module2");
-    Assert.assertEquals(BenchmarkRun.State.FAILED_COMPILE, benchmarkRun.getState());
-
-    Mockito.verify(errorReporter).sendEmail(Mockito.anyString());
-
-    waitFor(new Condition() {
-
-      @Override
-      public boolean condition() {
-        return !manager.isRunning();
-      }
-    }, 200);
-    Assert.assertFalse(manager.isRunning());
-    Assert.assertFalse(manager.isEventLoopAlive());
-  }
-
-  @Test
-  public void testOneFailedRunner() throws BenchmarkManagerException, InterruptedException {
-
-    Mockito.when(timerProvider.get()).thenReturn(timer);
-
-    manager = new BenchmarkManager(collector,
-        benchmarkWorkerFactory,
-        poolProvider,
-        reporterFactory,
-        true,
-        commitReader,
-        timerProvider,
-        errorReporter,
-        gwtSourceLocation);
-
-    Mockito.when(commitReader.getLastCommitId()).thenReturn("commit1");
-    Mockito.when(commitReader.getCurrentCommitId()).thenReturn("commit2");
-    Mockito.when(poolProvider.get()).thenReturn(threadPoolExecutor);
-    Mockito.when(collector.get()).thenReturn(Arrays.asList("module1", "module2"));
-
-    ArgumentCaptor<ProgressHandler> progressHandlerCaptor =
-        ArgumentCaptor.forClass(ProgressHandler.class);
-
-    ArgumentCaptor<BenchmarkWorkerConfig> workerConfigCaptor =
-        ArgumentCaptor.forClass(BenchmarkWorkerConfig.class);
-
-    Mockito.when(benchmarkWorkerFactory.create(workerConfigCaptor.capture(),
-        progressHandlerCaptor.capture())).thenReturn(benchmarkWorker);
-
-    ArgumentCaptor<Map<String, BenchmarkRun>> resultCaptor =
-        cast(ArgumentCaptor.forClass(Map.class));
-
-    ArgumentCaptor<ReportProgressHandler> reportProgressHandlerCaptor =
-        ArgumentCaptor.forClass(ReportProgressHandler.class);
-
-    Mockito.when(reporterFactory.create(resultCaptor.capture(), Mockito.anyString(),
-        reportProgressHandlerCaptor.capture())).thenReturn(benchmarkReporter);
-
-    Assert.assertFalse(manager.isRunning());
-
-    manager.start();
-
-    Assert.assertTrue(manager.isRunning());
-    Assert.assertTrue(manager.isEventLoopAlive());
-
-    ArgumentCaptor<TimerTask> captor = ArgumentCaptor.forClass(TimerTask.class);
-
-    Mockito.verify(timer).scheduleAtFixedRate(captor.capture(), Mockito.anyLong(),
-        Mockito.anyLong());
-
-    TimerTask timerTask = captor.getValue();
-    timerTask.run();
-
-    VerificationWithTimeout timeout = Mockito.timeout(200);
-    Mockito.verify(commitReader, timeout).checkout("commit1");
-    Mockito.verify(commitReader, timeout).maybeCheckoutNextCommit("commit1");
-    Mockito.verify(poolProvider, timeout).get();
-    Mockito.verify(collector, timeout).get();
-    Mockito.verify(threadPoolExecutor, timeout.times(2)).execute(benchmarkWorker);
-
-    List<ProgressHandler> progressHandlers = progressHandlerCaptor.getAllValues();
-    Assert.assertEquals(2, progressHandlers.size());
-    List<BenchmarkWorkerConfig> workerConfig = workerConfigCaptor.getAllValues();
-    Assert.assertEquals(2, workerConfig.size());
-
-    // TODO right now there is only one runner, needs updating
-    // simulate benchmarks done
-    progressHandlers.get(0).onResult(workerConfig.get(0).getRunners().get(0), 1);
-    progressHandlers.get(0).failedToRunBenchmark(workerConfig.get(0).getRunners().get(0),
-        "testerror");
-    progressHandlers.get(0).onRunEnded();
-    progressHandlers.get(1).onResult(workerConfig.get(0).getRunners().get(0), 2);
-    progressHandlers.get(1).onResult(workerConfig.get(1).getRunners().get(0), 23);
-    progressHandlers.get(1).onRunEnded();
-
-    Mockito.verifyZeroInteractions(benchmarkReporter);
-
-    Map<String, BenchmarkRun> map = manager.getLatestRun();
-    Assert.assertEquals(2, map.size());
-
-    Assert.assertTrue(map.containsKey("module1"));
-    Assert.assertTrue(map.containsKey("module2"));
-
-    BenchmarkRun benchmarkRun = map.get("module1");
-    Assert.assertEquals(BenchmarkRun.State.FAILED_TO_RUN_ON_RUNNER, benchmarkRun.getState());
-    benchmarkRun = map.get("module2");
-    Assert.assertEquals(BenchmarkRun.State.DONE, benchmarkRun.getState());
-
-    Mockito.verify(errorReporter).sendEmail(Mockito.anyString());
-
-    waitFor(new Condition() {
-
-      @Override
-      public boolean condition() {
-        return !manager.isRunning();
-      }
-    }, 200);
-    Assert.assertFalse(manager.isRunning());
-    Assert.assertFalse(manager.isEventLoopAlive());
-  }
-
-  @Test
-  public void testMapIsLocked() throws InterruptedException {
-
-    final List<String> list = new ArrayList<>();
-
-    final BenchmarkManagerWithPublicAdd manager = new BenchmarkManagerWithPublicAdd(collector,
-        benchmarkWorkerFactory,
-        poolProvider,
-        reporterFactory,
-        true,
-        commitReader,
-        timerProvider,
-        errorReporter,
-        gwtSourceLocation) {
-
-      @Override
-      void addBenchmarkRunSynchronized(Map<String, BenchmarkRun> runsByName, BenchmarkRun br) {
-        try {
-          Thread.sleep(40);
-        } catch (InterruptedException ignored) {
-        }
-        list.add("add");
-      }
-
-      @Override
-      Map<String, BenchmarkRun> getLatestRunSynchronized(Map<String, BenchmarkRun> runsByName) {
-        list.add("get");
-        return null;
-      }
-    };
-
-    Thread thread = new Thread(new Runnable() {
-
-      @Override
-      public void run() {
-        manager.addBenchmarkRun(null);
-      }
-    });
-
-    thread.start();
-
-    Thread.sleep(50);
-    manager.getLatestRun();
-
-    Assert.assertEquals(2, list.size());
-    Assert.assertEquals("add", list.get(0));
-    Assert.assertEquals("get", list.get(1));
-  }
-
-  private void waitFor(Condition c, long timeout) throws InterruptedException {
-    long endMs = System.currentTimeMillis() + timeout;
-    while (!c.condition()) {
-      if (System.currentTimeMillis() > endMs) {
-        throw new RuntimeException("timeout while waiting for condition");
-      }
-      Thread.sleep(5);
-    }
-  }
-}
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerTest.java b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerTest.java
deleted file mode 100644
index 6b18b14..0000000
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerTest.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.compileserver.server.manager;
-
-import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkWorker.ProgressHandler;
-import com.google.gwt.benchmark.compileserver.server.manager.Runner.Factory;
-import com.google.inject.Provider;
-
-import static org.mockito.Mockito.mock;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Arrays;
-
-/**
- * Test for {@link BenchmarkWorker}.
- */
-public class BenchmarkWorkerTest {
-
-  private String ip;
-  private int port;
-  private RunnerConfig runnerConfig;
-  private BenchmarkCompiler compiler;
-  private Factory runnerProvider;
-  private Runner runner;
-  private String moduleName;
-  private BenchmarkWorker worker;
-  private File benchmarkCompileOutputDir;
-  private ProgressHandler progressHandler;
-  private Provider<String> randomStringProvider;
-  private String moduleTemplate;
-  private BenchmarkWorkerConfig benchmarkData;
-  private File devJar;
-  private File userJar;
-
-  @Before
-  public void setup() {
-    moduleName = "moduleName1";
-
-    benchmarkCompileOutputDir = new File("./target/test/TestBenchmarkWorker/");
-    if (!benchmarkCompileOutputDir.exists()) {
-      if (!benchmarkCompileOutputDir.mkdirs()) {
-        Assert.fail("failed to create dirs");
-      }
-    }
-
-    ip = "127.0.0.1";
-    port = 8080;
-
-    runnerConfig = RunnerConfigs.CHROME_LINUX;
-
-    compiler = Mockito.mock(BenchmarkCompiler.class);
-    runnerProvider = Mockito.mock(Runner.Factory.class);
-
-    runner = Mockito.mock(Runner.class);
-
-    Mockito.when(runnerProvider.create(RunnerConfigs.CHROME_LINUX,
-        "http://" + ip + ":" + port + "/__bench/" + moduleName + ".html")).thenReturn(runner);
-
-    moduleTemplate = "{module_nocache}";
-
-
-    devJar = mock(File.class);
-    userJar = mock(File.class);
-
-    benchmarkData =
-        new BenchmarkWorkerConfig(moduleName, Arrays.asList(runnerConfig), devJar, userJar);
-
-    progressHandler = Mockito.mock(ProgressHandler.class);
-
-    randomStringProvider = BenchmarkManagerTest.cast(Mockito.mock(Provider.class));
-
-    worker = new BenchmarkWorker(compiler, runnerProvider, moduleTemplate, benchmarkData,
-        progressHandler, ip, port, benchmarkCompileOutputDir, randomStringProvider);
-  }
-
-  @After
-  public void tearDown() throws IOException {
-    FileUtils.deleteDirectory(benchmarkCompileOutputDir);
-  }
-
-  @Test
-  public void testCompilerError() throws BenchmarkCompilerException {
-
-    BenchmarkCompiler compiler = Mockito.mock(BenchmarkCompiler.class);
-    Factory runnerProvider = Mockito.mock(Runner.Factory.class);
-
-    Mockito.when(randomStringProvider.get()).thenReturn("randomDir1");
-
-    String moduleTemplate = "mytemplate [{module_nocache}]";
-    String moduleName = "moduleName1";
-    BenchmarkWorkerConfig benchmarkData = new BenchmarkWorkerConfig(moduleName,
-        Arrays.asList(RunnerConfigs.CHROME_LINUX), devJar, userJar);
-
-    ProgressHandler progressHandler = Mockito.mock(ProgressHandler.class);
-    String ip = "127.0.0.1";
-    File benchmarkCompileOutputDir = new File("./target/test/TestBenchmarkWorker");
-    File workDir = new File(benchmarkCompileOutputDir, "randomDir1");
-
-    BenchmarkWorker worker = new BenchmarkWorker(compiler, runnerProvider, moduleTemplate,
-        benchmarkData, progressHandler, ip, 8080, benchmarkCompileOutputDir, randomStringProvider);
-
-    Mockito.doThrow(new BenchmarkCompilerException("test")).when(compiler)
-        .compile(moduleName, workDir, devJar, userJar);
-
-    worker.run();
-
-    Mockito.verify(compiler).compile(Mockito.eq(moduleName), Mockito.<File> anyObject(),
-        Mockito.eq(devJar), Mockito.eq(userJar));
-    Mockito.verify(progressHandler).onCompilationFailed(Mockito.anyString());
-    Mockito.verifyZeroInteractions(runnerProvider);
-    Assert.assertFalse(workDir.exists());
-  }
-
-  @Test
-  public void testBenchmarkWorker() throws BenchmarkCompilerException {
-
-    Mockito.when(runner.isFailed()).thenReturn(false);
-    Mockito.when(runner.getResult()).thenReturn(Double.valueOf(1337));
-    Mockito.when(runner.getConfig()).thenReturn(runnerConfig);
-
-    Mockito.when(randomStringProvider.get()).thenReturn("randomDir1");
-
-    Mockito.when(runnerProvider.create(RunnerConfigs.CHROME_LINUX,
-        "http://" + ip + ":" + port + "/__bench/randomDir1/" + moduleName + ".html")).thenReturn(
-        runner);
-
-    worker.run();
-
-    File workDir = new File(benchmarkCompileOutputDir, "randomDir1");
-
-    Mockito.verify(compiler).compile(moduleName, workDir, devJar, userJar);
-
-    Mockito.verify(runnerProvider).create(runnerConfig,
-        "http://" + ip + ":" + port + "/__bench/randomDir1/" + moduleName + ".html");
-
-    Mockito.verify(progressHandler).onResult(runnerConfig, 1337);
-    Mockito.verify(progressHandler).onRunEnded();
-
-    Assert.assertFalse(workDir.exists());
-  }
-
-  @Test
-  public void testBenchmarkWorkerWithFailingRuns() throws BenchmarkCompilerException {
-
-    Mockito.when(runner.isFailed()).thenReturn(true);
-    Mockito.when(runner.getResult()).thenReturn(Double.valueOf(1337));
-    Mockito.when(runner.getConfig()).thenReturn(runnerConfig);
-    Mockito.when(randomStringProvider.get()).thenReturn("randomDir1");
-
-    Mockito.when(runnerProvider.create(RunnerConfigs.CHROME_LINUX,
-        "http://" + ip + ":" + port + "/__bench/randomDir1/" + moduleName + ".html")).thenReturn(
-        runner);
-
-    worker.run();
-    File workDir = new File(benchmarkCompileOutputDir, "randomDir1");
-
-    Mockito.verify(compiler).compile(moduleName, workDir, devJar, userJar);
-
-    Mockito.verify(runnerProvider).create(runnerConfig,
-        "http://" + ip + ":" + port + "/__bench/randomDir1/" + moduleName + ".html");
-
-    Mockito.verify(progressHandler).failedToRunBenchmark(Mockito.eq(runnerConfig),
-        Mockito.anyString());
-    Mockito.verify(progressHandler).onRunEnded();
-
-    Assert.assertFalse(workDir.exists());
-  }
-
-  @Test
-  public void testModuleFileIsBeingWritten() throws BenchmarkCompilerException,
-      FileNotFoundException, IOException {
-
-    worker = new BenchmarkWorker(compiler, runnerProvider, moduleTemplate, benchmarkData,
-        progressHandler, ip, port, benchmarkCompileOutputDir, randomStringProvider) {
-      @Override
-      void cleanupDirectory(File outputDir) {
-        // do nothing so we can see if the directory has the right content
-      }
-    };
-
-    Mockito.when(runner.isFailed()).thenReturn(false);
-    Mockito.when(runner.getResult()).thenReturn(Double.valueOf(1337));
-    Mockito.when(runner.getConfig()).thenReturn(runnerConfig);
-
-    Mockito.when(randomStringProvider.get()).thenReturn("randomDir1");
-
-    Mockito.when(runnerProvider.create(RunnerConfigs.CHROME_LINUX,
-        "http://" + ip + ":" + port + "/__bench/randomDir1/" + moduleName + ".html")).thenReturn(
-        runner);
-
-    worker.run();
-
-    File workDir = new File(benchmarkCompileOutputDir, "randomDir1");
-
-    Mockito.verify(compiler).compile(moduleName, workDir, devJar, userJar);
-
-    Mockito.verify(runnerProvider).create(runnerConfig,
-        "http://" + ip + ":" + port + "/__bench/randomDir1/" + moduleName + ".html");
-
-    Mockito.verify(progressHandler).onResult(runnerConfig, 1337);
-    Mockito.verify(progressHandler).onRunEnded();
-
-    Assert.assertTrue(workDir.exists());
-
-    String hostPageContent =
-        IOUtils.toString(new FileInputStream(new File(workDir, moduleName + ".html")), "UTF-8");
-
-    Assert.assertEquals(moduleName + "/" + moduleName + ".nocache.js", hostPageContent);
-  }
-}
diff --git a/compileserver/src/test/resources/scripts-working/compileModule b/compileserver/src/test/resources/scripts-working/compileModule
deleted file mode 100755
index c722a21..0000000
--- a/compileserver/src/test/resources/scripts-working/compileModule
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-set -e
-
-if [[ -z "${1}" && -z "${2}" && -z "${3}" && -z "${4}" && -z "${5}" ]]; then
-  echo "usage: compileModule moduleName dev.jar user.jar benchmark_src output_dir"
-  exit 1
-fi
-
-
-MODULE_NAME=${1}
-GWT_DEV_JAR=${2}
-GWT_USER_JAR=${3}
-BENCHMARKS_SRC=${4}
-OUTPUT_DIR=${5}
-
-#Echo back all the parameters so that we can verify them in test
-echo "${MODULE_NAME};${GWT_DEV_JAR};${GWT_USER_JAR};${BENCHMARKS_SRC};${OUTPUT_DIR}" > target/test-out
diff --git a/launcher/pom.xml b/launcher/pom.xml
deleted file mode 100644
index 43e6031..0000000
--- a/launcher/pom.xml
+++ /dev/null
@@ -1,125 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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/xsd/maven-4.0.0.xsd">
-
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>com.google.gwt.benchmark</groupId>
-  <artifactId>gwt-benchmark-launcher</artifactId>
-  <version>1.0-SNAPSHOT</version>
-  <packaging>jar</packaging>
-
-  <properties>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-  </properties>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-      <version>9.1.4.v20140401</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-      <version>9.1.4.v20140401</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gwt.benchmark</groupId>
-      <artifactId>gwt-benchmark-compileserver</artifactId>
-      <version>1.0-SNAPSHOT</version>
-      <type>war</type>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gwt.benchmark</groupId>
-      <artifactId>gwt-benchmark-common</artifactId>
-      <version>1.0-SNAPSHOT</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.api-client</groupId>
-      <artifactId>google-api-client-jackson2</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.api-client</groupId>
-      <artifactId>google-api-client</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.oauth-client</groupId>
-      <artifactId>google-oauth-client</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.oauth-client</groupId>
-      <artifactId>google-oauth-client-java6</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-      <version>18.0</version>
-    </dependency>
-    <!-- explicitly add the dependency here since the google api pulls in an old 
-      version -->
-    <!-- which does not work with webdriver -->
-    <dependency>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-      <version>4.2.1</version>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <version>2.5.1</version>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.7</source>
-          <target>1.7</target>
-        </configuration>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-eclipse-plugin</artifactId>
-        <version>2.8</version>
-        <configuration>
-          <downloadSources>true</downloadSources>
-          <downloadJavadocs>false</downloadJavadocs>
-          <projectnatures>
-            <projectnature>org.eclipse.jdt.core.javanature</projectnature>
-          </projectnatures>
-          <buildcommands>
-            <buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
-          </buildcommands>
-          <classpathContainers>
-            <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
-          </classpathContainers>
-        </configuration>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <version>2.7</version>
-        <executions>
-          <execution>
-            <id>unpack-dependencies</id>
-            <phase>generate-resources</phase>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <includeGroupIds>com.google.gwt.benchmark</includeGroupIds>
-              <includeArtifactIds>gwt-benchmark-compileserver</includeArtifactIds>
-              <outputDirectory>${project.build.directory}/war</outputDirectory>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-
-    </plugins>
-  </build>
-</project>
diff --git a/launcher/src/main/java/com/google/gwt/benchmark/launcher/Launcher.java b/launcher/src/main/java/com/google/gwt/benchmark/launcher/Launcher.java
deleted file mode 100644
index 483bd99..0000000
--- a/launcher/src/main/java/com/google/gwt/benchmark/launcher/Launcher.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 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.gwt.benchmark.launcher;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-
-/**
- * Launcher starts a jetty web server which runs the war of the compile server.
- */
-public class Launcher {
-  public static void main(String[] args) throws Exception {
-    // Create an embedded Jetty server on port 8080
-    Server server = new Server(8080);
-
-    // Use a context handler that allows for an exploded war
-    WebAppContext altHandler = new WebAppContext();
-    altHandler.setResourceBase("./target/war");
-    altHandler.setDescriptor("./target/war/WEB-INF/web.xml");
-    altHandler.setContextPath("/");
-    altHandler.setParentLoaderPriority(true);
-
-    // Add it to the server
-    server.setHandler(altHandler);
-
-    // And start it up
-    server.start();
-    server.join();
-  }
-}
diff --git a/oauth.sh b/oauth.sh
index 432a5c0..a17be34 100755
--- a/oauth.sh
+++ b/oauth.sh
@@ -7,8 +7,8 @@
 
 echo "Starting build"
 mvn -q install > /dev/null
-pushd launcher
+pushd cli
 trap '{ echo "Hey, you pressed Ctrl-C.  Time to quit." ; popd; exit 0; }' INT
 echo "Starting oauth tool"
-mvn exec:java -Dexec.mainClass=com.google.gwt.benchmark.launcher.OAuthWriter -Dexec.args="${1} ${2}"
+mvn exec:java -Dexec.mainClass=com.google.j2cl.benchmark.cli.OAuthWriter -Dexec.args="${1} ${2}"
 
diff --git a/pom.xml b/pom.xml
index 958d956..04f5610 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,18 +2,107 @@
          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>
+  <modelVersion>4.0.0</modelVersion>
 
-    <groupId>com.google.gwt.benchmark</groupId>
-    <artifactId>gwt-benchmark-parent</artifactId>
-    <packaging>pom</packaging>
-    <version>1.0-SNAPSHOT</version>
-    <name>GWT benchmarks parent project</name>
+  <groupId>com.google.gwt.benchmark</groupId>
+  <artifactId>gwt-benchmark-parent</artifactId>
+  <packaging>pom</packaging>
+  <version>1.0-SNAPSHOT</version>
+  <name>GWT benchmarks parent project</name>
 
-    <modules>
-        <module>benchmarks</module>
-        <module>common</module>
-        <module>compileserver</module>
-        <module>launcher</module>
-    </modules>
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>4.11</version>
+      </dependency>
+      <dependency>
+        <groupId>org.mockito</groupId>
+        <artifactId>mockito-all</artifactId>
+        <version>1.9.5</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.truth</groupId>
+        <artifactId>truth</artifactId>
+        <version>0.25</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.code.gson</groupId>
+        <artifactId>gson</artifactId>
+        <version>2.3.1</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.guava</groupId>
+        <artifactId>guava</artifactId>
+        <version>18.0</version>
+      </dependency>
+      <dependency>
+        <groupId>commons-io</groupId>
+        <artifactId>commons-io</artifactId>
+        <version>2.4</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.inject.extensions</groupId>
+        <artifactId>guice-servlet</artifactId>
+        <version>3.0</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.inject</groupId>
+        <artifactId>guice</artifactId>
+        <version>3.0</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.inject.extensions</groupId>
+        <artifactId>guice-assistedinject</artifactId>
+        <version>3.0</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <modules>
+      <module>benchmarks</module>
+      <module>common</module>
+      <module>server</module>
+      <module>cli</module>
+  </modules>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <version>2.5.1</version>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <configuration>
+            <source>1.7</source>
+            <target>1.7</target>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>2.3</version>
+          <executions>
+            <execution>
+              <phase>package</phase>
+              <goals>
+                <goal>jar</goal>
+              </goals>
+            </execution>
+          </executions>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-eclipse-plugin</artifactId>
+          <version>2.8</version>
+          <configuration>
+            <downloadSources>true</downloadSources>
+            <downloadJavadocs>false</downloadJavadocs>
+          </configuration>
+        </plugin>
+
+      </plugins>
+    </pluginManagement>
+  </build>
 </project>
diff --git a/server.sh b/server.sh
new file mode 100755
index 0000000..cfc5b19
--- /dev/null
+++ b/server.sh
@@ -0,0 +1,9 @@
+#!/bin/bash -e
+
+echo "Starting build"
+mvn -q clean install > /dev/null
+pushd server
+trap '{ echo "Hey, you pressed Ctrl-C.  Time to quit." ; popd; exit 0; }' INT
+echo "Starting server"
+mvn jetty:run \
+    -DconfigFile=./config/config
diff --git a/server/config/config b/server/config/config
new file mode 100644
index 0000000..f7510c9
--- /dev/null
+++ b/server/config/config
@@ -0,0 +1,4 @@
+seleniumHubUrl = http://localhost:4444/wd/hub
+extractDir = /tmp/benchmark_server/workdir/
+threadPoolSize = 5
+servletContainerPort = 8080
diff --git a/server/pom.xml b/server/pom.xml
new file mode 100644
index 0000000..1fde5d2
--- /dev/null
+++ b/server/pom.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.google.gwt.benchmark</groupId>
+  <artifactId>gwt-benchmarks-server</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>war</packaging>
+  
+  <parent>
+    <groupId>com.google.gwt.benchmark</groupId>
+    <artifactId>gwt-benchmark-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.gwt.benchmark</groupId>
+      <artifactId>gwt-benchmark-common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.seleniumhq.selenium</groupId>
+      <artifactId>selenium-java</artifactId>
+      <version>2.40.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.inject.extensions</groupId>
+      <artifactId>guice-servlet</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.inject</groupId>
+      <artifactId>guice</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.inject.extensions</groupId>
+      <artifactId>guice-assistedinject</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>9.1.4.v20140401</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-fileupload</groupId>
+      <artifactId>commons-fileupload</artifactId>
+      <version>1.3.1</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <version>2.3</version>
+        <configuration>
+          <webResources>
+            <resource>
+              <directory>${basedir}/src/main/webapp/WEB-INF</directory>
+              <filtering>true</filtering>
+              <targetPath>WEB-INF</targetPath>
+            </resource>
+          </webResources>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-eclipse-plugin</artifactId>
+        <version>2.8</version>
+        <configuration>
+          <downloadSources>true</downloadSources>
+          <downloadJavadocs>false</downloadJavadocs>
+          <buildOutputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+          </buildOutputDirectory>
+          <projectnatures>
+            <projectnature>org.eclipse.jdt.core.javanature</projectnature>
+            <projectnature>com.google.gdt.eclipse.core.webAppNature
+            </projectnature>
+          </projectnatures>
+          <buildcommands>
+            <buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
+            <buildcommand>com.google.gdt.eclipse.core.webAppProjectValidator
+            </buildcommand>
+          </buildcommands>
+          <classpathContainers>
+            <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER
+            </classpathContainer>
+          </classpathContainers>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-maven-plugin</artifactId>
+        <version>9.3.0.M2</version>
+        <configuration>
+          <scanIntervalSeconds>10</scanIntervalSeconds>
+          <webApp>
+            <contextPath>/</contextPath>
+          </webApp>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
+
+
diff --git a/server/src/main/java/com/google/j2cl/benchmark/server/BenchmarkUploadServlet.java b/server/src/main/java/com/google/j2cl/benchmark/server/BenchmarkUploadServlet.java
new file mode 100644
index 0000000..b4c8b47
--- /dev/null
+++ b/server/src/main/java/com/google/j2cl/benchmark/server/BenchmarkUploadServlet.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 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.j2cl.benchmark.server;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.j2cl.benchmark.common.runner.Job;
+import com.google.j2cl.benchmark.common.runner.JobId;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigJson;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigs;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet for uploading zip files for benchmarking.
+ */
+public class BenchmarkUploadServlet extends HttpServlet {
+
+  private static class JobIdResponse {
+    @SuppressWarnings("unused")
+    JobId jobId;
+  }
+
+  private static class JobResponse {
+    @SuppressWarnings("unused")
+    Job job;
+  }
+
+  private final ServerManager manager;
+
+  @Inject
+  public BenchmarkUploadServlet(ServerManager manager) {
+    this.manager = manager;
+  }
+
+  private static class ParsedRequest {
+    InputStream fileContent;
+    List<RunnerConfig> runnerIds;
+  }
+
+  @Override
+  protected void doPost(HttpServletRequest request, HttpServletResponse response)
+      throws ServletException, IOException {
+      List<FileItem> items = parseRequest(request);
+      ParsedRequest parsedRequest = parseFileItems(items);
+
+      JobId jobId = manager.submitJob(parsedRequest.fileContent, parsedRequest.runnerIds);
+
+      JobIdResponse resp = new JobIdResponse();
+      resp.jobId = jobId;
+
+      Gson gson = new Gson();
+      String json = gson.toJson(resp);
+
+      response.getWriter().write(json);
+  }
+
+  private ParsedRequest parseFileItems(List<FileItem> items) throws IOException, ServletException {
+    ParsedRequest parsedRequest = new ParsedRequest();
+    List<String> runners = null;
+    for (FileItem item : items) {
+      if (!item.isFormField()) {
+        if ("file".equals(item.getFieldName())) {
+          parsedRequest.fileContent = item.getInputStream();
+        }
+      } else {
+        if ("runnerIds".equals(item.getFieldName())) {
+          runners = Splitter.on(",").splitToList(item.getString());
+        }
+      }
+    }
+
+    if (parsedRequest.fileContent == null) {
+      throw new ServletException("No filed supplied");
+    }
+
+    if (runners == null) {
+      parsedRequest.runnerIds = RunnerConfigs.getAllRunners();
+    } else {
+      List<RunnerConfig> configs = Lists.newArrayList();
+      for (String runner : runners) {
+        configs.add(RunnerConfigs.fromString(runner));
+      }
+      parsedRequest.runnerIds = configs;
+    }
+    return parsedRequest;
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
+      IOException {
+    String jobIdsAsString = request.getParameter("jobId");
+
+    Job job;
+    try {
+      job = manager.getStatus(new JobId(jobIdsAsString));
+    } catch (JobNotFoundException e) {
+      throw new ServletException(e);
+    }
+
+
+    JobResponse jobResponse = new JobResponse();
+    jobResponse.job = job;
+
+    String outputJson = new GsonBuilder()
+        .registerTypeAdapter(RunnerConfig.class, new RunnerConfigJson())
+        .create()
+        .toJson(jobResponse);
+    response.getWriter().write(outputJson);
+  }
+
+  @VisibleForTesting
+  List<FileItem> parseRequest(HttpServletRequest request) throws ServletException {
+    try {
+      return new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
+    } catch (FileUploadException e) {
+      throw new ServletException("Can not parse upload data", e);
+    }
+  }
+}
diff --git a/common/src/main/java/com/google/gwt/benchmark/common/shared/service/ServiceException.java b/server/src/main/java/com/google/j2cl/benchmark/server/JobNotFoundException.java
similarity index 66%
rename from common/src/main/java/com/google/gwt/benchmark/common/shared/service/ServiceException.java
rename to server/src/main/java/com/google/j2cl/benchmark/server/JobNotFoundException.java
index 20e9807..2bab8e0 100644
--- a/common/src/main/java/com/google/gwt/benchmark/common/shared/service/ServiceException.java
+++ b/server/src/main/java/com/google/j2cl/benchmark/server/JobNotFoundException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Google Inc.
+ * Copyright 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
@@ -11,17 +11,11 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.common.shared.service;
+package com.google.j2cl.benchmark.server;
 
 /**
- * Generic base exception for all services.
+ * Signals that {@link ServerManager} can not find a job.
  */
-public class ServiceException extends Exception {
+public class JobNotFoundException extends Exception {
 
-  public ServiceException() {
-  }
-
-  public ServiceException(String message) {
-    super(message);
-  }
 }
diff --git a/server/src/main/java/com/google/j2cl/benchmark/server/ServerManager.java b/server/src/main/java/com/google/j2cl/benchmark/server/ServerManager.java
new file mode 100644
index 0000000..c65e239
--- /dev/null
+++ b/server/src/main/java/com/google/j2cl/benchmark/server/ServerManager.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 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.j2cl.benchmark.server;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.j2cl.benchmark.common.runner.Job;
+import com.google.j2cl.benchmark.common.runner.Job.FailReason;
+import com.google.j2cl.benchmark.common.runner.Job.Status;
+import com.google.j2cl.benchmark.common.runner.JobId;
+import com.google.j2cl.benchmark.common.runner.Runner;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
+import com.google.j2cl.benchmark.common.util.ZipUtil;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.logging.Logger;
+
+import javax.inject.Named;
+
+/**
+ * RunServerManager accepts JavaScript benchmarks (as a zip folder) and runs them using an
+ * implementation of {@link Runner} and reports back with results.
+ */
+public class ServerManager {
+
+  private static final Logger logger = Logger.getLogger(ServerManager.class.getCanonicalName());
+
+  private final Provider<String> randomStringProvider;
+
+  private final Map<String, Job> jobsById = new HashMap<>();
+
+  private final Runner.Factory runnerProvider;
+
+  private ExecutorService executorService;
+
+  private final int port;
+
+  private final String ip;
+
+  private final Provider<Long> timeProvider;
+
+  private final File extractDir;
+
+  private class Worker implements Runnable {
+    private final JobId jobId;
+    private RunnerConfig config;
+    private String url;
+    private final Runner.Factory runnerFactory;
+
+    public Worker(JobId jobId, RunnerConfig config, String url, Runner.Factory runnerFactory) {
+      this.jobId = jobId;
+      this.config = config;
+      this.url = url;
+      this.runnerFactory = runnerFactory;
+    }
+
+    @Override
+    public void run() {
+      Runner runner = runnerFactory.create(config, url);
+      runner.run();
+      if (runner.isFailed()) {
+        failed(jobId, config, runner.getErrorMessage());
+      } else {
+        finished(jobId, config, runner.getResult());
+      }
+    }
+  }
+
+  @Inject
+  public ServerManager(@Named("randomStringProvider") Provider<String> randomStringProvider,
+      Runner.Factory runnerProvider, ExecutorService executorService, @Named("ip") String ip,
+      @Named("port") int port, @Named("timeProvider") Provider<Long> timeProvider,
+      @Named("extractDir") File extractDir) {
+    this.randomStringProvider = randomStringProvider;
+    this.runnerProvider = runnerProvider;
+    this.executorService = executorService;
+    this.ip = ip;
+    this.port = port;
+    this.timeProvider = timeProvider;
+    this.extractDir = extractDir;
+
+    new Thread(new Runnable() {
+        @Override
+      public void run() {
+        long tenMinutesInMs = 1000l * 60 * 10;
+        try {
+          sleep(tenMinutesInMs);
+        } catch (InterruptedException e) {
+          return;
+        }
+        cleanup();
+      }
+    }).start();
+  }
+
+  public JobId submitJob(InputStream zipToBenchmark, List<RunnerConfig> runnerIds) {
+
+    String randomString = randomStringProvider.get();
+
+    File tempFile = null;
+    FileOutputStream tempOutputStream = null;
+    File folder = null;
+    try {
+      tempFile = File.createTempFile(randomString + "-to_bench", ".zip");
+      tempOutputStream = new FileOutputStream(tempFile);
+      folder = new File(extractDir, randomString);
+      folder.mkdirs();
+      logger.info("Using folder: " + folder.getAbsolutePath());
+
+      IOUtils.copy(zipToBenchmark, tempOutputStream);
+      ZipUtil.unzip(tempFile, folder);
+    } catch (IOException e) {
+      Job job = new Job(new JobId(randomString), runnerIds, timeProvider.get());
+      job.setFailed(FailReason.CAN_NOT_EXTRACT_ZIP);
+      addJob(job);
+      return job.getJobId();
+    } finally {
+      IOUtils.closeQuietly(tempOutputStream);
+      if (tempFile != null) {
+        tempFile.delete();
+      }
+    }
+
+    Job job = new Job(new JobId(randomString), runnerIds, timeProvider.get());
+    job.setFolder(folder);
+
+    for (RunnerConfig runnerConfig : runnerIds) {
+      String url = getUrl(randomString);
+      Worker worker = new Worker(job.getJobId(), runnerConfig, url, runnerProvider);
+      executorService.submit(worker);
+    }
+    jobsById.put(job.getJobId().getId(), job);
+    return job.getJobId();
+  }
+
+  public synchronized Job getStatus(JobId jobId) throws JobNotFoundException {
+    Job job = jobsById.get(jobId.getId());
+    if (job == null) {
+      throw new JobNotFoundException();
+    }
+    return job.clone();
+  }
+
+  private synchronized void addJob(Job job) {
+    jobsById.put(job.getJobId().getId(), job);
+  }
+
+  private String getUrl(String dirName) {
+    String url = "http://{host}:{port}/__bench/{dirName}/index.html";
+    url = url.replace("{host}", ip);
+    url = url.replace("{port}", "" + port);
+    url = url.replace("{dirName}", dirName);
+    return url;
+  }
+
+  private synchronized void finished(JobId jobId, RunnerConfig config, double result) {
+    Job job = jobsById.get(jobId.getId());
+    if (job == null) {
+      throw new IllegalStateException(
+          "Job reports being finished, but can not be found. This should never happen. JobId: "
+          + jobId.getId() + " config: " + config);
+    }
+    job.addResult(config, result);
+    maybeCleanUp(job);
+  }
+
+  private synchronized void failed(JobId jobId, RunnerConfig config, String reason) {
+    Job job = jobsById.get(jobId.getId());
+    if (job == null) {
+      throw new IllegalStateException(
+          "Job reports being failed, but can not be found. This should never happen. JobId: "
+          + jobId.getId() + " config: " + config);
+    }
+    job.setRunFailed(config, reason);
+    maybeCleanUp(job);
+  }
+
+  private void maybeCleanUp(Job job) {
+    if (job.getStatus() == Status.FINISHED || job.getStatus() == Status.FAILED) {
+      FileUtils.deleteQuietly(job.getFolder());
+    }
+  }
+
+  @VisibleForTesting
+  void sleep(long timeInMs) throws InterruptedException {
+    Thread.sleep(timeInMs);
+  }
+
+  @VisibleForTesting
+  synchronized void cleanup() {
+    Iterator<Job> iterator = jobsById.values().iterator();
+    while (iterator.hasNext()) {
+      Job job = iterator.next();
+      if (job.isOld(timeProvider.get())) {
+        iterator.remove();
+      }
+    }
+  }
+}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/WebDriverRunner.java b/server/src/main/java/com/google/j2cl/benchmark/server/WebDriverRunner.java
similarity index 96%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/WebDriverRunner.java
rename to server/src/main/java/com/google/j2cl/benchmark/server/WebDriverRunner.java
index f2a1598..36b9268 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/WebDriverRunner.java
+++ b/server/src/main/java/com/google/j2cl/benchmark/server/WebDriverRunner.java
@@ -11,10 +11,12 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.manager;
+package com.google.j2cl.benchmark.server;
 
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.name.Named;
+import com.google.j2cl.benchmark.common.runner.Runner;
+import com.google.j2cl.benchmark.common.runner.RunnerConfig;
 
 import org.openqa.selenium.remote.DesiredCapabilities;
 import org.openqa.selenium.remote.RemoteWebDriver;
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/GuiceServletConfig.java b/server/src/main/java/com/google/j2cl/benchmark/server/guice/GuiceServletConfig.java
similarity index 80%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/GuiceServletConfig.java
rename to server/src/main/java/com/google/j2cl/benchmark/server/guice/GuiceServletConfig.java
index 7ca4002..1c4ab21 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/GuiceServletConfig.java
+++ b/server/src/main/java/com/google/j2cl/benchmark/server/guice/GuiceServletConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Google Inc.
+ * Copyright 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
@@ -11,9 +11,8 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.guice;
+package com.google.j2cl.benchmark.server.guice;
 
-import com.google.gwt.benchmark.compileserver.server.runners.settings.Settings;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.servlet.GuiceServletContextListener;
@@ -27,7 +26,7 @@
  */
 public class GuiceServletConfig extends GuiceServletContextListener {
 
-  private static final Logger logger = Logger.getLogger(CompileServerGuiceModule.class.getName());
+  private static final Logger logger = Logger.getLogger(ServerServletModule.class.getName());
 
   private static Settings createSettings() {
     try {
@@ -48,7 +47,7 @@
   protected Injector getInjector() {
     Settings settings = createSettings();
     return Guice.createInjector(
-        new CompileServerGuiceModule(settings.getBenchmarkCompileOutputDir()),
-        new BenchmarkModule(settings));
+        new ServerModule(settings),
+        new ServerServletModule(settings.getExtractDir()));
   }
 }
diff --git a/server/src/main/java/com/google/j2cl/benchmark/server/guice/ServerModule.java b/server/src/main/java/com/google/j2cl/benchmark/server/guice/ServerModule.java
new file mode 100644
index 0000000..9794338
--- /dev/null
+++ b/server/src/main/java/com/google/j2cl/benchmark/server/guice/ServerModule.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 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.j2cl.benchmark.server.guice;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+import com.google.inject.name.Names;
+import com.google.j2cl.benchmark.common.runner.Runner;
+import com.google.j2cl.benchmark.server.WebDriverRunner;
+
+import java.io.File;
+import java.net.URL;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * The compile server guice module.
+ */
+public class ServerModule extends AbstractModule {
+
+  private final Settings settings;
+
+  public ServerModule(Settings settings) {
+    this.settings = settings;
+  }
+
+  @Override
+  protected void configure() {
+    install(new FactoryModuleBuilder().implement(Runner.class, WebDriverRunner.class).build(
+        Runner.Factory.class));
+    bind(String.class).annotatedWith(Names.named("randomStringProvider")).toProvider(
+        RandomStringProvider.class);
+    bind(Integer.class).annotatedWith(Names.named("poolSize")).toInstance(
+        settings.getThreadPoolSize());
+    bind(Integer.class).annotatedWith(Names.named("port")).toInstance(
+        settings.getServletContainerPort());
+    bind(Long.class).annotatedWith(Names.named("timeProvider")).toProvider(TimeProvider.class);
+    bind(String.class).annotatedWith(Names.named("ip")).toInstance(settings.getIpAddress());
+    bind(URL.class).annotatedWith(Names.named("hubUrl")).toInstance(settings.getHubUrl());
+    bind(File.class).annotatedWith(Names.named("extractDir")).toInstance(settings.getExtractDir());
+    bind(ExecutorService.class).toInstance(
+        Executors.newFixedThreadPool(settings.getThreadPoolSize()));
+  }
+
+  private static class RandomStringProvider implements Provider<String> {
+    @Override
+    public String get() {
+      return UUID.randomUUID().toString();
+    }
+  }
+
+  private static class TimeProvider implements Provider<Long> {
+    @Override
+    public Long get() {
+      return System.currentTimeMillis();
+    }
+  }
+}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java b/server/src/main/java/com/google/j2cl/benchmark/server/guice/ServerServletModule.java
similarity index 62%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java
rename to server/src/main/java/com/google/j2cl/benchmark/server/guice/ServerServletModule.java
index 1db527a..13d192c 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java
+++ b/server/src/main/java/com/google/j2cl/benchmark/server/guice/ServerServletModule.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Google Inc.
+ * Copyright 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
@@ -11,12 +11,11 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.benchmark.compileserver.server.guice;
+package com.google.j2cl.benchmark.server.guice;
 
-import com.google.gwt.benchmark.compileserver.server.service.BenchmarkServiceImpl;
-import com.google.gwt.benchmark.compileserver.server.service.FileUploadServlet;
 import com.google.inject.Singleton;
 import com.google.inject.servlet.ServletModule;
+import com.google.j2cl.benchmark.server.BenchmarkUploadServlet;
 
 import org.eclipse.jetty.servlet.DefaultServlet;
 
@@ -27,7 +26,7 @@
 /**
  * The guice module for all our servlets.
  */
-public class CompileServerGuiceModule extends ServletModule {
+public class ServerServletModule extends ServletModule {
 
   private static Map<String, String> createServletParams(String resourceBaseAbsolutePath) {
     Map<String, String> initParams = new HashMap<String, String>();
@@ -37,22 +36,19 @@
     return initParams;
   }
 
-  private final File benchmarkOutputDir;
+  private final File extractDir;
 
-  public CompileServerGuiceModule(File benchmarkOutputDir) {
-    this.benchmarkOutputDir = benchmarkOutputDir;
+  public ServerServletModule(File extractDir) {
+    this.extractDir = extractDir;
   }
 
   @Override
   protected void configureServlets() {
     bind(DefaultServlet.class).in(Singleton.class);
     serve("/__bench/*").with(DefaultServlet.class,
-        createServletParams(benchmarkOutputDir.getAbsolutePath()));
+        createServletParams(extractDir.getAbsolutePath()));
 
-    bind(BenchmarkServiceImpl.class).in(Singleton.class);
-    serve("/compileserver/data/service").with(BenchmarkServiceImpl.class);
-
-    bind(FileUploadServlet.class).in(Singleton.class);
-    serve("/compileserver/upload").with(FileUploadServlet.class);
+    bind(BenchmarkUploadServlet.class).in(Singleton.class);
+    serve("/upload").with(BenchmarkUploadServlet.class);
   }
 }
diff --git a/server/src/main/java/com/google/j2cl/benchmark/server/guice/Settings.java b/server/src/main/java/com/google/j2cl/benchmark/server/guice/Settings.java
new file mode 100644
index 0000000..d43db14
--- /dev/null
+++ b/server/src/main/java/com/google/j2cl/benchmark/server/guice/Settings.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 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.j2cl.benchmark.server.guice;
+
+import com.google.j2cl.benchmark.common.util.Util;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.net.URL;
+import java.util.Properties;
+
+/**
+ * Setting class containing all the configuration for the benchmarking system.
+ */
+public class Settings {
+
+  public static Settings parseSettings(File settingsFile) throws Exception {
+    Settings settings = new Settings();
+    Properties prop = new Properties();
+    FileInputStream stream = null;
+    try {
+      stream = new FileInputStream(settingsFile);
+      // load a properties file from class path, inside static method
+      prop.load(stream);
+      settings.hubUrl = new URL(prop.getProperty("seleniumHubUrl"));
+      settings.threadPoolSize = Integer.parseInt(prop.getProperty("threadPoolSize"));
+      settings.servletContainerPort = Integer.parseInt(prop.getProperty("servletContainerPort"));
+      settings.ipAddress = Util.getFirstNonLoopbackAddress().getHostAddress();
+      settings.extractDir = new File(prop.getProperty("extractDir"));
+      if (!settings.extractDir.exists() && !settings.extractDir.mkdirs()) {
+        throw new RuntimeException("Can not create dir");
+      }
+    } finally {
+      IOUtils.closeQuietly(stream);
+    }
+    return settings;
+  }
+
+  private String ipAddress;
+  private URL hubUrl;
+  private int threadPoolSize;
+  private int servletContainerPort;
+  private File extractDir;
+
+  public String getIpAddress() {
+    return ipAddress;
+  }
+
+  public URL getHubUrl() {
+    return hubUrl;
+  }
+
+  public int getThreadPoolSize() {
+    return threadPoolSize;
+  }
+
+  public int getServletContainerPort() {
+    return servletContainerPort;
+  }
+
+  public File getExtractDir() {
+    return extractDir;
+  }
+
+  private Settings() {}
+}
diff --git a/compileserver/src/main/webapp/WEB-INF/web.xml b/server/src/main/webapp/WEB-INF/web.xml
similarity index 85%
rename from compileserver/src/main/webapp/WEB-INF/web.xml
rename to server/src/main/webapp/WEB-INF/web.xml
index 02e51c4..d19001c 100644
--- a/compileserver/src/main/webapp/WEB-INF/web.xml
+++ b/server/src/main/webapp/WEB-INF/web.xml
@@ -18,6 +18,6 @@
   </filter-mapping>
 
   <listener>
-    <listener-class>com.google.gwt.benchmark.compileserver.server.guice.GuiceServletConfig</listener-class>
+    <listener-class>com.google.j2cl.benchmark.server.guice.GuiceServletConfig</listener-class>
   </listener>
 </web-app>
diff --git a/server/src/main/webapp/index.html b/server/src/main/webapp/index.html
new file mode 100644
index 0000000..da32c3c
--- /dev/null
+++ b/server/src/main/webapp/index.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<html>
+  <body>
+    Benchmarking server
+  </body>
+</html>
\ No newline at end of file
diff --git a/server/src/test/java/com/google/j2cl/benchmark/server/BenchmarkUploadServletTest.java b/server/src/test/java/com/google/j2cl/benchmark/server/BenchmarkUploadServletTest.java
new file mode 100644
index 0000000..8c7d447
--- /dev/null
+++ b/server/src/test/java/com/google/j2cl/benchmark/server/BenchmarkUploadServletTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 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.j2cl.benchmark.server;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.j2cl.benchmark.common.runner.Job;
+import com.google.j2cl.benchmark.common.runner.JobId;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigs;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.fileupload.FileItem;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Test for {@link BenchmarkUploadServlet}.
+ */
+public class BenchmarkUploadServletTest {
+  private BenchmarkUploadServlet servlet;
+  private ServerManager runServerManager;
+  private List<FileItem> fileItems;
+
+  @Before
+  public void before() {
+    fileItems = Lists.newArrayList();
+    runServerManager = mock(ServerManager.class);
+
+    servlet = new BenchmarkUploadServlet(runServerManager) {
+      @Override
+      List<FileItem> parseRequest(HttpServletRequest request) throws ServletException {
+        return fileItems;
+      }
+    };
+  }
+
+  @Test
+  public void testSimpleUpload() throws ServletException, IOException {
+    FileItem fileItem = mock(FileItem.class);
+    when(fileItem.getFieldName()).thenReturn("file");
+    fileItems.add(fileItem);
+    InputStream inputStream = mock(InputStream.class);
+    when(fileItem.getInputStream()).thenReturn(inputStream);
+    when(fileItem.isFormField()).thenReturn(false);
+
+    FileItem runners = mock(FileItem.class);
+    when(runners.getFieldName()).thenReturn("runnerIds");
+    when(runners.getString())
+        .thenReturn(Joiner.on(",").join(Lists.newArrayList(
+            RunnerConfigs.CHROME_LINUX.toString(), RunnerConfigs.FIREFOX_LINUX.toString())));
+    when(runners.isFormField()).thenReturn(true);
+    fileItems.add(runners);
+
+    when(
+        runServerManager.submitJob(
+            inputStream, Arrays.asList(RunnerConfigs.CHROME_LINUX, RunnerConfigs.FIREFOX_LINUX)))
+        .thenReturn(new JobId("foo"));
+    HttpServletRequest request = mock(HttpServletRequest.class);
+    HttpServletResponse response = mock(HttpServletResponse.class);
+    PrintWriter writer = mock(PrintWriter.class);
+    when(response.getWriter()).thenReturn(writer);
+    servlet.doPost(request, response);
+    verify(writer).write("{\"jobId\":{\"id\":\"foo\"}}");
+  }
+
+  @Test
+  public void testSimpleJobRetrieval() throws JobNotFoundException, IOException, ServletException {
+    JobId jobId = new JobId("jobId1");
+    Job job = new Job(jobId, RunnerConfigs.getAllRunners(), 1);
+    when(runServerManager.getStatus(jobId)).thenReturn(job);
+
+    HttpServletRequest request = mock(HttpServletRequest.class);
+    when(request.getParameter("jobId")).thenReturn(jobId.getId());
+    HttpServletResponse response = mock(HttpServletResponse.class);
+    PrintWriter writer = mock(PrintWriter.class);
+    when(response.getWriter()).thenReturn(writer);
+    servlet.doGet(request, response);
+    verify(writer).write("{\"job\":{\"jobId\":{\"id\":\"jobId1\"},\"status\":\"CREATED\","
+        + "\"counter\":0,\"failedCounter\":0,\"expectedResults\":4,\"jobResultsByRunnerId\":"
+        + "{\"windows ie IE11\":{\"succeded\":false,\"result\":0.0,\"ran\":false,\"runnerConfig\":"
+        + "\"windows ie IE11\"},\"windows ie IE10\":{\"succeded\":false,\"result\":0.0,\"ran\":"
+        + "false,\"runnerConfig\":\"windows ie IE10\"},\"linux chrome\":{\"succeded\":false,"
+        + "\"result\":0.0,\"ran\":false,\"runnerConfig\":\"linux chrome\"},\"linux firefox\""
+        + ":{\"succeded\":false,\"result\":0.0,\"ran\":false,\"runnerConfig\":\"linux firefox\"}}"
+        + ",\"runnerConfigs\":[\"linux firefox\",\"linux chrome\",\"windows ie IE10\""
+        + ",\"windows ie IE11\"],\"creationTimeInMsEpoch\":1}}");
+  }
+
+  @Test
+  public void testJobNotFound() throws JobNotFoundException, IOException {
+    JobId jobId = new JobId("jobId1");
+    when(runServerManager.getStatus(jobId)).thenThrow(new JobNotFoundException());
+
+    HttpServletRequest request = mock(HttpServletRequest.class);
+    when(request.getParameter("jobId")).thenReturn(jobId.getId());
+    HttpServletResponse response = mock(HttpServletResponse.class);
+    PrintWriter writer = mock(PrintWriter.class);
+    when(response.getWriter()).thenReturn(writer);
+    try {
+      servlet.doGet(request, response);
+      fail("expected exception not thrown");
+    } catch (ServletException e) {
+      assertThat(e.getCause()).isInstanceOf(JobNotFoundException.class);
+    }
+  }
+}
diff --git a/server/src/test/java/com/google/j2cl/benchmark/server/ServerManagerTest.java b/server/src/test/java/com/google/j2cl/benchmark/server/ServerManagerTest.java
new file mode 100644
index 0000000..e2ed561
--- /dev/null
+++ b/server/src/test/java/com/google/j2cl/benchmark/server/ServerManagerTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 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.j2cl.benchmark.server;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.inject.Provider;
+import com.google.j2cl.benchmark.common.runner.Job;
+import com.google.j2cl.benchmark.common.runner.JobId;
+import com.google.j2cl.benchmark.common.runner.JobResult;
+import com.google.j2cl.benchmark.common.runner.Runner;
+import com.google.j2cl.benchmark.common.runner.Runner.Factory;
+import com.google.j2cl.benchmark.server.JobNotFoundException;
+import com.google.j2cl.benchmark.server.ServerManager;
+import com.google.j2cl.benchmark.common.runner.RunnerConfigs;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Test for {@link ServerManager}.
+ */
+public class ServerManagerTest {
+
+  private ServerManager manager;
+  private Provider<Long> timeProvider;
+  private Provider<String> randomStringProvider;
+  private ExecutorService executorService;
+  private Factory runnerFactory;
+  private AtomicBoolean stopSleeping;
+  private AtomicBoolean fileShouldHaveBeenDeleted;
+  private File extractDir;
+
+  @SuppressWarnings("unchecked")
+  @Before
+  public void before() {
+    randomStringProvider = mock(Provider.class);
+
+    executorService = mock(ExecutorService.class);
+    timeProvider = mock(Provider.class);
+    runnerFactory = mock(Runner.Factory.class);
+    stopSleeping = new AtomicBoolean(false);
+    fileShouldHaveBeenDeleted = new AtomicBoolean(false);
+    extractDir = new File("target/");
+
+  }
+
+  @Test
+  public void testSuccessfulRun() throws JobNotFoundException {
+
+    manager = new ServerManager(randomStringProvider, runnerFactory, executorService, "myhost",
+        7777, timeProvider, extractDir);
+
+    when(randomStringProvider.get()).thenReturn("r1", "r2");
+
+    InputStream benchmarkZip = this.getClass().getResourceAsStream("benchmark.zip");
+
+    when(timeProvider.get()).thenReturn(0l, 1l, 2l);
+
+    Runner chromeRunner = mock(Runner.class);
+    when(runnerFactory.create(RunnerConfigs.CHROME_LINUX,
+        "http://myhost:7777/__bench/r1/index.html")).thenReturn(chromeRunner);
+    when(chromeRunner.isDone()).thenReturn(true);
+    when(chromeRunner.isFailed()).thenReturn(false);
+    when(chromeRunner.getResult()).thenReturn(2.0);
+
+    Runner ffRunner = mock(Runner.class);
+    when(runnerFactory.create(RunnerConfigs.FIREFOX_LINUX,
+        "http://myhost:7777/__bench/r1/index.html")).thenReturn(ffRunner);
+    when(ffRunner.isDone()).thenReturn(true);
+    when(ffRunner.isFailed()).thenReturn(false);
+    when(ffRunner.getResult()).thenReturn(3.0);
+
+    JobId jobId = manager.submitJob(benchmarkZip,
+        Arrays.asList(RunnerConfigs.CHROME_LINUX, RunnerConfigs.FIREFOX_LINUX));
+
+    ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+
+    verify(executorService, times(2)).submit(argumentCaptor.capture());
+
+    List<Runnable> allRunnables = argumentCaptor.getAllValues();
+    assertThat(allRunnables.size()).isEqualTo(2);
+
+    // execute chrome
+    allRunnables.get(0).run();
+
+    Job job = manager.getStatus(jobId);
+
+    assertThat(job.getJobId()).isSameAs(jobId);
+    assertThat(job.getStatus()).isEqualTo(Job.Status.RUNNING);
+    JobResult jobResultChrome = job.getResult(RunnerConfigs.CHROME_LINUX);
+    assertThat(jobResultChrome).isNotNull();
+    assertThat(jobResultChrome.isRan()).isTrue();
+    assertThat(jobResultChrome.isSucceded()).isTrue();
+    assertThat(jobResultChrome.getResult()).isLessThan(2.00001);
+    assertThat(jobResultChrome.getResult()).isGreaterThan(1.99999);
+
+    // execute firefox
+    allRunnables.get(1).run();
+
+    job = manager.getStatus(jobId);
+
+    assertThat(job.getJobId()).isSameAs(jobId);
+    assertThat(job.getStatus()).isEqualTo(Job.Status.FINISHED);
+    JobResult jobResultFF = job.getResult(RunnerConfigs.FIREFOX_LINUX);
+    assertThat(jobResultFF).isNotNull();
+    assertThat(jobResultFF.isRan()).isTrue();
+    assertThat(jobResultFF.isSucceded()).isTrue();
+    assertThat(jobResultFF.getResult()).isLessThan(3.00001);
+    assertThat(jobResultFF.getResult()).isGreaterThan(2.99999);
+
+    FileUtils.deleteQuietly(job.getFolder());
+  }
+
+  @Test
+  public void testFailingRun() throws JobNotFoundException {
+
+    manager = new ServerManager(randomStringProvider, runnerFactory, executorService, "myhost",
+        7777, timeProvider, extractDir);
+
+    when(randomStringProvider.get()).thenReturn("r1", "r2");
+
+    InputStream benchmarkZip = this.getClass().getResourceAsStream("benchmark.zip");
+
+    when(timeProvider.get()).thenReturn(0l, 1l, 2l);
+
+    Runner chromeRunner = mock(Runner.class);
+    when(runnerFactory.create(RunnerConfigs.CHROME_LINUX,
+        "http://myhost:7777/__bench/r1/index.html")).thenReturn(chromeRunner);
+    when(chromeRunner.isDone()).thenReturn(true);
+    when(chromeRunner.isFailed()).thenReturn(true);
+    when(chromeRunner.getErrorMessage()).thenReturn("error message");
+
+    Runner ffRunner = mock(Runner.class);
+    when(runnerFactory.create(RunnerConfigs.FIREFOX_LINUX,
+        "http://myhost:7777/__bench/r1/index.html")).thenReturn(ffRunner);
+    when(ffRunner.isDone()).thenReturn(true);
+    when(ffRunner.isFailed()).thenReturn(false);
+    when(ffRunner.getResult()).thenReturn(3.0);
+
+    JobId jobId = manager.submitJob(benchmarkZip,
+        Arrays.asList(RunnerConfigs.CHROME_LINUX, RunnerConfigs.FIREFOX_LINUX));
+
+    ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+
+    verify(executorService, times(2)).submit(argumentCaptor.capture());
+
+    List<Runnable> allRunnables = argumentCaptor.getAllValues();
+    assertThat(allRunnables.size()).isEqualTo(2);
+
+    // execute chrome
+    allRunnables.get(0).run();
+
+    Job job = manager.getStatus(jobId);
+
+    assertThat(job.getJobId()).isSameAs(jobId);
+    assertThat(job.getStatus()).isEqualTo(Job.Status.RUNNING);
+    JobResult jobResultChrome = job.getResult(RunnerConfigs.CHROME_LINUX);
+    assertThat(jobResultChrome).isNotNull();
+    assertThat(jobResultChrome.isRan()).isTrue();
+    assertThat(jobResultChrome.isSucceded()).isFalse();
+    assertThat(jobResultChrome.getErrorMessage()).isEqualTo("error message");
+
+    // execute firefox
+    allRunnables.get(1).run();
+
+    job = manager.getStatus(jobId);
+
+    assertThat(job.getJobId()).isSameAs(jobId);
+    assertThat(job.getStatus()).isEqualTo(Job.Status.FAILED);
+    JobResult jobResultFF = job.getResult(RunnerConfigs.FIREFOX_LINUX);
+    assertThat(jobResultFF).isNotNull();
+    assertThat(jobResultFF.isRan()).isTrue();
+    assertThat(jobResultFF.isSucceded()).isTrue();
+    assertThat(jobResultFF.getResult()).isLessThan(3.00001);
+    assertThat(jobResultFF.getResult()).isGreaterThan(2.99999);
+
+    FileUtils.deleteQuietly(job.getFolder());
+  }
+
+  @Test
+  public void testCleanup() throws InterruptedException, JobNotFoundException {
+
+    manager = new ServerManager(randomStringProvider, runnerFactory, executorService, "myhost",
+        7777, timeProvider, extractDir) {
+        @Override
+      void sleep(long timeInMs) throws InterruptedException {
+        while (!stopSleeping.get()) {
+          Thread.sleep(100l);
+        }
+      }
+
+        @Override
+      synchronized void cleanup() {
+        super.cleanup();
+        fileShouldHaveBeenDeleted.set(true);
+      }
+
+    };
+
+    when(randomStringProvider.get()).thenReturn("r1", "r2");
+
+    InputStream benchmarkZip = this.getClass().getResourceAsStream("benchmark.zip");
+
+    when(timeProvider.get()).thenReturn(0l, 1l, 2l);
+
+    Runner chromeRunner = mock(Runner.class);
+    when(runnerFactory.create(RunnerConfigs.CHROME_LINUX,
+        "http://myhost:7777/__bench/r1/index.html")).thenReturn(chromeRunner);
+    when(chromeRunner.isDone()).thenReturn(true);
+    when(chromeRunner.isFailed()).thenReturn(true);
+    when(chromeRunner.getErrorMessage()).thenReturn("error message");
+
+    Runner ffRunner = mock(Runner.class);
+    when(runnerFactory.create(RunnerConfigs.FIREFOX_LINUX,
+        "http://myhost:7777/__bench/r1/index.html")).thenReturn(ffRunner);
+    when(ffRunner.isDone()).thenReturn(true);
+    when(ffRunner.isFailed()).thenReturn(false);
+    when(ffRunner.getResult()).thenReturn(3.0);
+
+    JobId jobId = manager.submitJob(benchmarkZip,
+        Arrays.asList(RunnerConfigs.CHROME_LINUX, RunnerConfigs.FIREFOX_LINUX));
+
+    ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+
+    verify(executorService, times(2)).submit(argumentCaptor.capture());
+
+    List<Runnable> allRunnables = argumentCaptor.getAllValues();
+    assertThat(allRunnables.size()).isEqualTo(2);
+
+    // execute chrome
+    allRunnables.get(0).run();
+
+    Job job = manager.getStatus(jobId);
+
+    assertThat(job.getJobId()).isSameAs(jobId);
+    assertThat(job.getStatus()).isEqualTo(Job.Status.RUNNING);
+    JobResult jobResultChrome = job.getResult(RunnerConfigs.CHROME_LINUX);
+    assertThat(jobResultChrome).isNotNull();
+    assertThat(jobResultChrome.isRan()).isTrue();
+    assertThat(jobResultChrome.isSucceded()).isFalse();
+    assertThat(jobResultChrome.getErrorMessage()).isEqualTo("error message");
+
+    // execute firefox
+    allRunnables.get(1).run();
+
+    job = manager.getStatus(jobId);
+
+    assertThat(job.getJobId()).isSameAs(jobId);
+    assertThat(job.getStatus()).isEqualTo(Job.Status.FAILED);
+    JobResult jobResultFF = job.getResult(RunnerConfigs.FIREFOX_LINUX);
+    assertThat(jobResultFF).isNotNull();
+    assertThat(jobResultFF.isRan()).isTrue();
+    assertThat(jobResultFF.isSucceded()).isTrue();
+    assertThat(jobResultFF.getResult()).isLessThan(3.00001);
+    assertThat(jobResultFF.getResult()).isGreaterThan(2.99999);
+
+    stopSleeping.set(true);
+
+    // wait for the cleanup thread to delete the folder
+    Thread.sleep(400);
+    assertThat(fileShouldHaveBeenDeleted.get()).isTrue();
+    assertThat(job.getFolder().exists()).isFalse();
+  }
+}
diff --git a/server/src/test/resources/com/google/j2cl/benchmark/server/benchmark.zip b/server/src/test/resources/com/google/j2cl/benchmark/server/benchmark.zip
new file mode 100644
index 0000000..6286ec3
--- /dev/null
+++ b/server/src/test/resources/com/google/j2cl/benchmark/server/benchmark.zip
Binary files differ
diff --git a/start.sh b/start.sh
deleted file mode 100755
index 4592f8e..0000000
--- a/start.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash -e
-
-echo "Starting build"
-mvn -q clean install > /dev/null
-pushd launcher
-trap '{ echo "Hey, you pressed Ctrl-C.  Time to quit." ; popd; exit 0; }' INT
-echo "Starting server"
-mvn exec:exec \
-    -Dexec.executable="java" \
-    -Dexec.args="-classpath %classpath -DconfigFile=../../compileserver_config -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5555 com.google.gwt.benchmark.launcher.Launcher"
-