Added compile server frontend.

Change-Id: Iaa39d7973dde94f44a4f14b36acf0f86b24986d5
diff --git a/compileserver/pom.xml b/compileserver/pom.xml
index aa1c2ba..84a16f7 100644
--- a/compileserver/pom.xml
+++ b/compileserver/pom.xml
@@ -72,6 +72,12 @@
       <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>
   </dependencies>
   <build>
     <plugins>
@@ -131,6 +137,26 @@
           </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>
 
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
new file mode 100644
index 0000000..fee9817
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/CompileServer.gwt.xml
@@ -0,0 +1,26 @@
+<!--                                                                        -->
+<!-- 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"/>
+
+  <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
new file mode 100644
index 0000000..af3e126
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/CompileServerEntryPoint.java
@@ -0,0 +1,62 @@
+/*
+ * 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.ui.Label;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.inject.Provides;
+
+
+/**
+ * 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();
+    }
+  }
+
+  @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
new file mode 100644
index 0000000..89a4bad
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.java
@@ -0,0 +1,277 @@
+/*
+ * 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.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.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;
+
+/**
+ * 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;
+
+
+  @Inject
+  public BenchmarkStatusComposite(ServiceAsync service, NumberFormat format,
+      Provider<Label> labelProvider) {
+    this.service = service;
+    this.labelProvider = labelProvider;
+    this.format = format;
+    initWidget(uiBinder.createAndBindUi(this));
+    bundle.css().ensureInjected();
+  }
+
+  public void start() {
+    loadBenchmarks();
+  }
+
+  @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(callback);
+    } else {
+      service.startServer(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(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
new file mode 100644
index 0000000..4423d5c
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.ui.xml
@@ -0,0 +1,12 @@
+<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: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
new file mode 100644
index 0000000..15f0fc9
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/benchmarkstatus.css
@@ -0,0 +1,19 @@
+.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
index 92751ea..edac4ad 100644
--- 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
@@ -94,7 +94,7 @@
     private int poolSize;
 
     @Inject
-    public PoolProvider(@Named("poolsize") int poolSize) {
+    public PoolProvider(@Named("poolSize") int poolSize) {
       this.poolSize = poolSize;
     }
 
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java
index 5da8fbd..07bcf30 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java
@@ -13,6 +13,7 @@
  */
 package com.google.gwt.benchmark.compileserver.server.guice;
 
+import com.google.gwt.benchmark.compileserver.server.service.BenchmarkServiceImpl;
 import com.google.inject.Singleton;
 import com.google.inject.servlet.ServletModule;
 
@@ -46,5 +47,8 @@
     bind(DefaultServlet.class).in(Singleton.class);
     serve("/__bench/*").with(DefaultServlet.class,
         createServletParams(benchmarkOutputDir.getAbsolutePath()));
+
+    bind(BenchmarkServiceImpl.class).in(Singleton.class);
+    serve("/compileserver/data/service").with(BenchmarkServiceImpl.class);
   }
 }
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
new file mode 100644
index 0000000..5e20d9a
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/BenchmarkServiceImpl.java
@@ -0,0 +1,151 @@
+/*
+ * 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.compileserver.server.manager.BenchmarkManager;
+import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkRun;
+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.ServiceException;
+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.getOS() + " " + rc.getBrowser());
+    }
+    return runnerNames;
+  }
+
+  private static final Logger logger = Logger.getLogger(BenchmarkServiceImpl.class.getName());
+
+  private BenchmarkManager benchmarkManager;
+
+  @Inject
+  public BenchmarkServiceImpl(BenchmarkManager benchmarkManager) {
+    this.benchmarkManager = benchmarkManager;
+  }
+
+  @Override
+  public BenchmarkOverviewResponseDTO loadBenchmarkOverview() throws ServiceException {
+    try {
+      BenchmarkOverviewResponseDTO response = new BenchmarkOverviewResponseDTO();
+      response.setRunnerNames(createRunnerDTOs(benchmarkManager.getAllRunners()));
+
+      Map<String, BenchmarkRun> latestRun = benchmarkManager.getLatestRun();
+      response.setExecutingBenchmarks(benchmarkManager.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() {
+    benchmarkManager.start();
+  }
+
+  @Override
+  public void stopServer() {
+    benchmarkManager.stop();
+  }
+}
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
new file mode 100644
index 0000000..89eeadc
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/Service.java
@@ -0,0 +1,31 @@
+/*
+ * 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.RemoteService;
+import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
+
+/**
+ * Service interface implemented server side.
+ */
+@RemoteServiceRelativePath("data/service")
+public interface Service extends RemoteService {
+
+  BenchmarkOverviewResponseDTO loadBenchmarkOverview() throws ServiceException;
+
+  void startServer();
+
+  void stopServer();
+}
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
new file mode 100644
index 0000000..1789575
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceAsync.java
@@ -0,0 +1,28 @@
+/*
+ * 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(AsyncCallback<BenchmarkOverviewResponseDTO> callback);
+
+  void startServer(AsyncCallback<Void> callback);
+
+  void stopServer(AsyncCallback<Void> callback);
+}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceException.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceException.java
new file mode 100644
index 0000000..55d1ea6
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceException.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * Generic base exception for all services.
+ */
+public class ServiceException extends Exception {
+
+  public ServiceException() {
+  }
+
+  public ServiceException(String message) {
+    super(message);
+  }
+}
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
new file mode 100644
index 0000000..77030cd
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkOverviewEntryDTO.java
@@ -0,0 +1,67 @@
+/*
+ * 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
new file mode 100644
index 0000000..d03d2d1
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkOverviewResponseDTO.java
@@ -0,0 +1,63 @@
+/*
+ * 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
new file mode 100644
index 0000000..d54ac07
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/dto/BenchmarkRunDTO.java
@@ -0,0 +1,55 @@
+/*
+ * 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
new file mode 100644
index 0000000..82f0f21
--- /dev/null
+++ b/compileserver/src/main/webapp/index.html
@@ -0,0 +1,10 @@
+<!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
new file mode 100644
index 0000000..191df6c
--- /dev/null
+++ b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusCompositeTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.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) {
+      @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(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(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));
+  }
+}