Add dashboard frontend.

Change-Id: Ib91e07c4aee6f1a15f1406a05d3dffa9197da43a
diff --git a/common/pom.xml b/common/pom.xml
index b409233..60ad7e0 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -16,8 +16,10 @@
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <gwtversion>2.6.0</gwtversion>
+    <gwtversion>2.6.1</gwtversion>
   </properties>
+  
+  
 
   <dependencies>
     <dependency>
@@ -45,6 +47,18 @@
   </dependencies>
 
   <build>
+    <resources>
+      <resource>
+        <directory>src/main/java</directory>
+        <includes>
+          <include>**/client/**</include>
+          <include>**/shared/**</include>
+          <include>**/*.gwt.xml</include>
+          <include>**/public/**</include>
+        </includes>
+      </resource>
+    </resources>
+  
     <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
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
index 939aa30..cd0107d 100644
--- 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
@@ -14,7 +14,7 @@
 
 <module>
   <inherits name="com.google.gwt.core.Core" />
-  <inherits name="com.google.gwt.autobeans.AutoBeans"/>
+  <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
index b595760..03d71ed 100644
--- 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
@@ -29,7 +29,7 @@
 
   public String getRunnerId();
 
-  public double getRunsPerMinute();
+  public double getRunsPerSecond();
 
-  public void setRunsPerMinute(double runsPerMinute);
+  public void setRunsPerSecond(double runsPerSecond);
 }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceException.java b/common/src/main/java/com/google/gwt/benchmark/common/shared/service/ServiceException.java
similarity index 92%
rename from compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceException.java
rename to common/src/main/java/com/google/gwt/benchmark/common/shared/service/ServiceException.java
index 55d1ea6..20e9807 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceException.java
+++ b/common/src/main/java/com/google/gwt/benchmark/common/shared/service/ServiceException.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.shared;
+package com.google.gwt.benchmark.common.shared.service;
 
 /**
  * Generic base exception for all services.
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
index fee9817..2ddef1c 100644
--- 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
@@ -15,6 +15,7 @@
 <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" />
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporter.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporter.java
index 83a3e2e..7a2804b 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporter.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporter.java
@@ -120,7 +120,7 @@
 
     BenchmarkRunJson runJSON = factory.run().as();
     runJSON.setCommitId(commitId);
-    runJSON.setCommitTimeMsEpoch((double)commitMsEpoch);
+    runJSON.setCommitTimeMsEpoch(commitMsEpoch);
     Map<String, List<BenchmarkResultJson>> results = new LinkedHashMap<>();
 
     for (Entry<String, BenchmarkRun> br : this.results.entrySet()) {
@@ -135,7 +135,7 @@
         BenchmarkResultJson resultJSON = factory.result().as();
         resultJSON.setBenchmarkName(moduleName);
         resultJSON.setRunnerId(runnerConfig.toString());
-        resultJSON.setRunsPerMinute(result.getRunsPerSecond());
+        resultJSON.setRunsPerSecond(result.getRunsPerSecond());
         list.add(resultJSON);
       }
 
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
index 5e20d9a..3de55ea 100644
--- 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
@@ -13,12 +13,12 @@
  */
 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.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;
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
index 89eeadc..e6ff809 100644
--- 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
@@ -13,6 +13,7 @@
  */
 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;
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporterTest.java b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporterTest.java
index ab44ec3..1d6c91f 100644
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporterTest.java
+++ b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkReporterTest.java
@@ -113,21 +113,21 @@
     List<BenchmarkResultJson> module1List = resultsJSON.get("module1");
     Assert.assertEquals(2, module1List.size());
     Assert.assertEquals("module1", module1List.get(0).getBenchmarkName());
-    Assert.assertEquals(2, module1List.get(0).getRunsPerMinute(), 0.0001);
+    Assert.assertEquals(2, module1List.get(0).getRunsPerSecond(), 0.0001);
     Assert.assertEquals(RunnerConfigs.CHROME_LINUX.toString(),
         module1List.get(0).getRunnerId().toString());
     Assert.assertEquals("module1", module1List.get(1).getBenchmarkName());
-    Assert.assertEquals(3, module1List.get(1).getRunsPerMinute(), 0.0001);
+    Assert.assertEquals(3, module1List.get(1).getRunsPerSecond(), 0.0001);
     Assert.assertEquals(RunnerConfigs.FIREFOX_LINUX.toString(),
         module1List.get(1).getRunnerId().toString());
 
     List<BenchmarkResultJson> module1List2 = resultsJSON.get("module2");
     Assert.assertEquals("module2", module1List2.get(0).getBenchmarkName());
-    Assert.assertEquals(4, module1List2.get(0).getRunsPerMinute(), 0.0001);
+    Assert.assertEquals(4, module1List2.get(0).getRunsPerSecond(), 0.0001);
     Assert.assertEquals(RunnerConfigs.CHROME_LINUX.toString(),
         module1List2.get(0).getRunnerId().toString());
     Assert.assertEquals("module2", module1List2.get(1).getBenchmarkName());
-    Assert.assertEquals(5, module1List2.get(1).getRunsPerMinute(), 0.0001);
+    Assert.assertEquals(5, module1List2.get(1).getRunsPerSecond(), 0.0001);
     Assert.assertEquals(RunnerConfigs.FIREFOX_LINUX.toString(),
         module1List2.get(1).getRunnerId().toString());
 
diff --git a/dashboard/pom.xml b/dashboard/pom.xml
index 1fc29a1..7a492bf 100644
--- a/dashboard/pom.xml
+++ b/dashboard/pom.xml
@@ -175,6 +175,11 @@
       <artifactId>commons-io</artifactId>
       <version>2.4</version>
     </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava-gwt</artifactId>
+      <version>17.0</version>
+    </dependency>
 
     <dependency>
       <groupId>junit</groupId>
@@ -188,6 +193,12 @@
       <version>1.9.5</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.google.gwt.gwtmockito</groupId>
+      <artifactId>gwtmockito</artifactId>
+      <version>1.1.3</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
 
@@ -257,7 +268,6 @@
             <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>
@@ -267,6 +277,7 @@
             <exclude>org.datanucleus:datanucleus-jpa</exclude>
             <exclude>org.datanucleus:datanucleus-api-jdo</exclude>
             <exclude>com.google.appengine.orm:datanucleus-appengine</exclude>
+            <exclude>com.google.appengine:appengine-tools-sdk</exclude>
           </excludes>
 
 
@@ -292,6 +303,24 @@
         </executions>
       </plugin>
 
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>gwt-maven-plugin</artifactId>
+        <version>${gwtversion}</version>
+        <executions>
+          <execution>
+            <configuration>
+              <module>com.google.gwt.benchmark.dashboard.Dashboard</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/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/Dashboard.gwt.xml b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/Dashboard.gwt.xml
new file mode 100644
index 0000000..55d255b
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/Dashboard.gwt.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.6.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.6.0/distro-source/core/src/gwt-module.dtd">
+<module rename-to="dashboard">
+  <inherits name="com.google.gwt.user.User" />
+  <inherits name="com.google.gwt.activity.Activity"/>
+  <inherits name="com.google.gwt.inject.Inject"/>
+  <inherits name='com.google.gwt.visualization.Visualization'/>
+  <inherits name="com.google.common.collect.Collect"/>
+  <inherits name="com.google.gwt.benchmark.common.Common" />
+  <source path="client" excludes='**/*Test.java'/>
+  <source path="shared" />
+  <entry-point class="com.google.gwt.benchmark.dashboard.client.DashBoardEntryPoint"/>
+
+  <!-- This can be removed for GWT 2.7 -->
+  <add-linker name="xsiframe" />
+  <set-configuration-property name="devModeRedirectEnabled"
+    value="true" />
+</module>
\ No newline at end of file
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/DashBoardEntryPoint.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/DashBoardEntryPoint.java
new file mode 100644
index 0000000..257b032
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/DashBoardEntryPoint.java
@@ -0,0 +1,103 @@
+/*
+ * 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.dashboard.client;
+
+import com.google.gwt.benchmark.dashboard.client.ui.GraphComposite;
+import com.google.gwt.benchmark.dashboard.client.ui.ModuleOverviewComposite;
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+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.History;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.inject.Provides;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+import javax.inject.Singleton;
+
+/**
+ * EntryPoint for dashboard UI.
+ */
+public class DashBoardEntryPoint implements EntryPoint {
+
+  private Injector injector;
+  private SimplePanel simplePanel;
+
+
+  @GinModules(Module.class)
+  public interface Injector extends Ginjector {
+
+    EventBus getEventBus();
+
+    ModuleOverviewComposite getModuleOverviewComposite();
+
+    GraphComposite getGraphComposite();
+  }
+
+  public static class Module extends AbstractGinModule {
+
+    @Override
+    protected void configure() {
+      bind(EventBus.class).to(SimpleEventBus.class).in(Singleton.class);
+    }
+
+    @Provides
+    protected Label createLabel() {
+      return new Label();
+    }
+
+    @Provides
+    protected CheckBox createCheckBox() {
+      return new CheckBox();
+    }
+  }
+
+  @Override
+  public void onModuleLoad() {
+    injector = GWT.create(Injector.class);
+    simplePanel = new SimplePanel();
+    RootPanel.get().add(simplePanel);
+
+
+    String token = History.getToken();
+    handleHistory(token);
+
+    History.addValueChangeHandler(new ValueChangeHandler<String>() {
+
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
+          handleHistory(event.getValue());
+      }
+    });
+  }
+
+  private void handleHistory(String token) {
+    if(token.startsWith("!graph?")) {
+      GraphComposite graphComposite = injector.getGraphComposite();
+      simplePanel.setWidget(graphComposite);
+      graphComposite.start();
+    } else {
+      ModuleOverviewComposite moduleOverviewComposite = injector.getModuleOverviewComposite();
+      simplePanel.setWidget(moduleOverviewComposite);
+      moduleOverviewComposite.start();
+    }
+  }
+}
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/GraphComposite.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/GraphComposite.java
new file mode 100644
index 0000000..7252f51
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/GraphComposite.java
@@ -0,0 +1,328 @@
+/*
+ * 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.dashboard.client.ui;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
+import com.google.gwt.benchmark.dashboard.shared.service.DashboardServiceAsync;
+import com.google.gwt.benchmark.dashboard.shared.service.dto.BenchmarkResultsTable;
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+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.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.visualization.client.AbstractDataTable.ColumnType;
+import com.google.gwt.visualization.client.DataTable;
+import com.google.gwt.visualization.client.visualizations.LineChart.Options;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * This widget fetches benchmark runs from the server and renders a graph.
+ */
+public class GraphComposite extends Composite {
+
+  interface Binder extends UiBinder<Widget, GraphComposite> {
+  }
+
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  private boolean graphWidgetLoaded;
+  private boolean dataReady;
+  private BenchmarkResultsTable result;
+  private Set<String> runnerIds = new TreeSet<>();
+
+  private final List<HandlerRegistration> handlers = new ArrayList<HandlerRegistration>();
+
+  private String benchmarkName;
+  private int week;
+  private int year;
+
+  private final HistoryAccessor history;
+  private final DashboardServiceAsync service;
+  private final Provider<CheckBox> checkboxProvider;
+
+  @UiField
+  Button backButton;
+
+  @UiField
+  Button forwardButton;
+
+  @UiField
+  Panel checkBoxContainer;
+
+  @UiField
+  GraphWidget graphWidget;
+
+  @UiField
+  Label errorLabel;
+
+  @UiField
+  Label loadingLabel;
+
+  @UiField
+  Widget container;
+
+  @UiField
+  Label weekLabel;
+
+  @Inject
+  public GraphComposite(DashboardServiceAsync service, Provider<CheckBox> checkboxProvider,
+      HistoryAccessor history) {
+    this.service = service;
+    this.checkboxProvider = checkboxProvider;
+    this.history = history;
+    initWidget(uiBinder.createAndBindUi(this));
+  }
+
+  public void start() {
+    graphWidgetLoaded = false;
+    dataReady = false;
+    if (parseHistory(history.getToken())) {
+      loadData();
+
+      graphWidget.init(new Runnable() {
+
+          @Override
+        public void run() {
+          graphWidgetLoaded = true;
+          maybeRender();
+        }
+      });
+    }
+  }
+
+  private void maybeRender() {
+    if (!graphWidgetLoaded || !dataReady) {
+      return;
+    }
+    resetView();
+    renderCheckBoxes();
+    renderGraph();
+  }
+
+  private void renderGraph() {
+    if (result.getColumnCount() == 0) {
+      graphWidget.clear();
+      return;
+    }
+
+    DataTable data = graphWidget.createData();
+    data.addColumn(ColumnType.STRING, "Commits");
+
+    for (String runnerId : result.getAllRunnerIds()) {
+      if (!runnerIds.contains(runnerId)) {
+        continue;
+      }
+      data.addColumn(ColumnType.NUMBER, runnerId);
+    }
+
+    data.addRows(result.getRowCount());
+    for (int i = 0; i < result.getRowCount(); i++) {
+      data.setValue(i, 0, result.getCommitIds().get(i));
+    }
+
+    int skippedColumn = 0;
+    for (int columnIndex = 0; columnIndex < result.getColumnCount(); columnIndex++) {
+      if (!runnerIds.contains(result.getRunnerId(columnIndex))) {
+        skippedColumn++;
+        continue;
+      }
+      for (int rowIndex = 0; rowIndex < result.getRowCount(); rowIndex++) {
+        data.setValue(rowIndex, columnIndex + 1 - skippedColumn,
+            result.getRunsPerSecond(columnIndex, rowIndex));
+      }
+    }
+
+    Options options = graphWidget.createOptions();
+    options.setWidth(800);
+    options.setHeight(600);
+    options.setTitle(result.getBenchmarkName());
+    graphWidget.displayChart(options, data);
+  }
+
+  private void renderCheckBoxes() {
+    for (final String runner : result.getAllRunnerIds()) {
+      CheckBox checkBox = checkboxProvider.get();
+      checkBox.setValue(runnerIds.contains(runner));
+      checkBox.setText(runner);
+      handlers.add(checkBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+
+        @Override
+        public void onValueChange(ValueChangeEvent<Boolean> event) {
+          onCheckboxClicked(runner, event.getValue());
+        }
+      }));
+      checkBoxContainer.add(checkBox);
+    }
+  }
+
+  private void resetView() {
+    for (HandlerRegistration hr : handlers) {
+      hr.removeHandler();
+    }
+    handlers.clear();
+    checkBoxContainer.clear();
+    container.setVisible(true);
+    weekLabel.setText(result.getWeekName());
+  }
+
+  private void onCheckboxClicked(String runners, boolean value) {
+    if (value) {
+      runnerIds.add(runners);
+    } else {
+      runnerIds.remove(runners);
+    }
+
+    if (runnerIds.isEmpty()) {
+      runnerIds = new TreeSet<>(result.getAllRunnerIds());
+    }
+    history.newItem(createHistoryToken(), false);
+    maybeRender();
+  }
+
+  private void loadData() {
+    loadingLabel.setVisible(true);
+    errorLabel.setVisible(false);
+    container.setVisible(false);
+
+    AsyncCallback<BenchmarkResultsTable> callback = new AsyncCallback<BenchmarkResultsTable>() {
+
+      @Override
+      public void onFailure(Throwable caught) {
+        errorLabel.setText("Error while fetching graphs: " + caught.getMessage());
+        errorLabel.setVisible(true);
+        loadingLabel.setVisible(false);
+      }
+
+      @Override
+      public void onSuccess(BenchmarkResultsTable result) {
+        loadingLabel.setVisible(false);
+        GraphComposite.this.result = result;
+        if (GraphComposite.this.runnerIds.isEmpty()) {
+          GraphComposite.this.runnerIds =  new TreeSet<String>(result.getAllRunnerIds());
+        }
+        GraphComposite.this.week = result.getWeek();
+        GraphComposite.this.year = result.getYear();
+        dataReady = true;
+        maybeRender();
+      }
+    };
+
+    if (week == -1 || year == -1) {
+      service.getLatestGraphs(benchmarkName, callback);
+    } else {
+      service.getGraphs(benchmarkName, week, year, callback);
+    }
+  }
+
+  //Visible for testing
+  @UiHandler("backButton")
+  void onBackButtonClicked(@SuppressWarnings("unused") ClickEvent e) {
+    // until the next time we have 54 weeks in a year (2028),
+    // this system will not be running anymore
+    // and even then we won't be working over new year either.
+    if (week - 1 < 1) {
+      year -= 1;
+      week = 53;
+    } else {
+      week -= 1;
+    }
+
+    history.newItem(createHistoryToken(), false);
+    loadData();
+  }
+
+  // Visible for testing
+  @UiHandler("forwardButton")
+  void onForwardButtonClicked(@SuppressWarnings("unused") ClickEvent e) {
+    // until the next time we have 54 weeks in a year (2028),
+    // this system will not be running anymore
+    // and even then we won't be working over new year either.
+    if (week + 1 > 53) {
+      year += 1;
+      week = 1;
+    } else {
+      week += 1;
+    }
+
+    history.newItem(createHistoryToken(), false);
+    loadData();
+  }
+
+  private String createHistoryToken() {
+    ImmutableMap<String, Object> params = ImmutableMap.<String, Object> of(
+        "benchmark", benchmarkName, "w", week, "y", year, "rids", runnerIds);
+    return "!graph?" + Joiner.on("&").withKeyValueSeparator("=").join(params);
+  }
+
+  private boolean parseHistory(String token) {
+    if (!token.startsWith("!graph?")) {
+      history.newItem("", true);
+      return false;
+    }
+
+    token = token.substring("!graph?".length());
+    Map<String, String> split = null;
+    try {
+      split = Splitter.on("&").withKeyValueSeparator("=").split(token);
+    } catch (IllegalArgumentException e) {
+      history.newItem("", true);
+      return false;
+    }
+
+    benchmarkName = split.get("benchmark");
+    if (benchmarkName == null) {
+      history.newItem("", true);
+      return false;
+    }
+
+    String weekString = split.get("w");
+    String yearString = split.get("y");
+
+    if (weekString == null || yearString == null) {
+      year = -1;
+      week = -1;
+      runnerIds = new TreeSet<>();
+      return true;
+    }
+
+    week = Integer.parseInt(weekString);
+    year = Integer.parseInt(yearString);
+
+    Splitter splitter = Splitter.on(",").trimResults(
+        CharMatcher.anyOf("[]").or(CharMatcher.WHITESPACE));
+
+    runnerIds = new TreeSet<String>(splitter.splitToList(split.get("rids")));
+    return true;
+  }
+}
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/GraphComposite.ui.xml b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/GraphComposite.ui.xml
new file mode 100644
index 0000000..68bcb7f
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/GraphComposite.ui.xml
@@ -0,0 +1,18 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:gw='urn:import:com.google.gwt.benchmark.dashboard.client.ui'>
+
+  <g:HTMLPanel>
+    <g:HTMLPanel ui:field="container">
+      <g:Button text="back" ui:field="backButton" />
+      <g:InlineLabel ui:field="weekLabel" />
+      <g:Button text="forward" ui:field="forwardButton"/>
+      <g:FlowPanel ui:field="checkBoxContainer"/>
+      <gw:GraphWidget ui:field="graphWidget" />
+    </g:HTMLPanel>
+    <g:Label ui:field="errorLabel"/>
+    <g:Label ui:field="loadingLabel">
+      Loading
+    </g:Label>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/GraphWidget.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/GraphWidget.java
new file mode 100644
index 0000000..bd7b4ce
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/GraphWidget.java
@@ -0,0 +1,54 @@
+/*
+ * 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.dashboard.client.ui;
+
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.visualization.client.DataTable;
+import com.google.gwt.visualization.client.VisualizationUtils;
+import com.google.gwt.visualization.client.visualizations.LineChart;
+import com.google.gwt.visualization.client.visualizations.LineChart.Options;
+
+/**
+ * This widget provides access to the visualization API.
+ */
+public class GraphWidget extends Composite {
+
+  private SimplePanel chartContainer = new SimplePanel();
+
+  public GraphWidget() {
+    initWidget(chartContainer);
+  }
+
+  public DataTable createData() {
+    return DataTable.create();
+  }
+
+  public Options createOptions() {
+    return Options.create();
+  }
+
+  public void clear() {
+    chartContainer.clear();
+  }
+
+  public void displayChart(Options options, DataTable data) {
+    LineChart lineChart = new LineChart(data, options);
+    chartContainer.setWidget(lineChart);
+  }
+
+  public void init(Runnable r) {
+    VisualizationUtils.loadVisualizationApi(r, LineChart.PACKAGE);
+  }
+}
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/HistoryAccessor.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/HistoryAccessor.java
new file mode 100644
index 0000000..b6ffc5b
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/HistoryAccessor.java
@@ -0,0 +1,36 @@
+/*
+ * 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.dashboard.client.ui;
+
+import com.google.gwt.user.client.History;
+import com.google.gwt.user.client.Window;
+
+/**
+ * HistoryAccessor is a thin wrapper around GWT's history implementation.
+ */
+public class HistoryAccessor {
+  public String getToken() {
+    return History.getToken();
+  }
+
+  public void newItem(String token, boolean issueEvent) {
+    History.newItem(token, issueEvent);
+  }
+
+  public void replaceItem(String token, @SuppressWarnings("unused") boolean issueEvent) {
+    // TODO switch to new History implementation in GWT 2.7
+    // This will not work without xsiframe linker on some browsers
+    Window.Location.replace("!#" + token);
+  }
+}
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/ModuleOverviewComposite.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/ModuleOverviewComposite.java
new file mode 100644
index 0000000..8767b16
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/ModuleOverviewComposite.java
@@ -0,0 +1,113 @@
+/*
+ * 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.dashboard.client.ui;
+
+import com.google.gwt.benchmark.dashboard.shared.service.DashboardServiceAsync;
+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.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Panel;
+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;
+
+/**
+ * This widget displays the overview of all benchmarks.
+ */
+public class ModuleOverviewComposite extends Composite {
+
+  interface Binder extends UiBinder<Widget, ModuleOverviewComposite> {
+  }
+
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  private final DashboardServiceAsync service;
+  private final Provider<Label> labelProvider;
+  private final List<HandlerRegistration> handlers = new ArrayList<>();
+  private final HistoryAccessor history;
+
+  @UiField
+  Panel contentContainer;
+
+  @UiField
+  Label errorLabel;
+
+  @UiField
+  Label loadingLabel;
+
+  @Inject
+  public ModuleOverviewComposite(DashboardServiceAsync service, Provider<Label> labelProvider, HistoryAccessor history) {
+    this.service = service;
+    this.labelProvider = labelProvider;
+    this.history = history;
+    initWidget(uiBinder.createAndBindUi(this));
+  }
+
+  public void start() {
+    loadBenchmarks();
+  }
+
+  private void loadBenchmarks() {
+    errorLabel.setVisible(false);
+    loadingLabel.setVisible(true);
+    contentContainer.setVisible(false);
+
+    service.getLatestBenchmarkNames(new AsyncCallback<ArrayList<String>>() {
+
+      @Override
+      public void onFailure(Throwable caught) {
+        errorLabel.setVisible(true);
+        errorLabel.setText("Can not load benchmarks");
+        loadingLabel.setVisible(false);
+      }
+
+      @Override
+      public void onSuccess(ArrayList<String> result) {
+        renderGraphList(result);
+      }
+    });
+  }
+
+  private void renderGraphList(List<String> benchmarkNames) {
+    loadingLabel.setVisible(false);
+    contentContainer.setVisible(true);
+    contentContainer.clear();
+
+    for (final String benchmarkName : benchmarkNames) {
+      Label label = labelProvider.get();
+      handlers.add(label.addClickHandler(new ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          ModuleOverviewComposite.this.onClick(benchmarkName);
+        }
+      }));
+      label.setText(benchmarkName);
+      contentContainer.add(label);
+    }
+  }
+
+  private void onClick(String module) {
+    history.newItem("!graph?benchmark=" + module, true);
+  }
+}
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/ModuleOverviewComposite.ui.xml b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/ModuleOverviewComposite.ui.xml
new file mode 100644
index 0000000..beba7cf
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/client/ui/ModuleOverviewComposite.ui.xml
@@ -0,0 +1,7 @@
+<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="errorLabel" />
+    <g:Label ui:field="loadingLabel" />
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkController.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkController.java
index 938f32a..f9462df 100644
--- a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkController.java
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkController.java
@@ -35,16 +35,21 @@
 import com.google.gwt.benchmark.dashboard.server.domain.BenchmarkResult;
 import com.google.gwt.benchmark.dashboard.server.domain.BenchmarkRun;
 import com.google.gwt.benchmark.dashboard.server.guice.DashboardServletGuiceModule;
+import com.google.gwt.benchmark.dashboard.shared.service.dto.BenchmarkResultsTable;
+
+import org.apache.commons.lang.ArrayUtils;
 
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.ConcurrentModificationException;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -93,6 +98,43 @@
     }
   }
 
+  /**
+   * Returns the name of each benchmark included in the most
+   * recent benchmark run.
+   */
+  public List<String> getLatestBenchmarkNames() throws ControllerException {
+    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
+
+    Query query = BenchmarkResult.createQueryLastest();
+
+    PreparedQuery prepare = ds.prepare(query);
+    List<Entity> list = prepare.asList(FetchOptions.Builder.withLimit(1));
+
+    if (list.size() == 0) {
+      throw new ControllerException("No entries in datastore");
+    }
+
+    BenchmarkRun run = new BenchmarkRun(list.get(0));
+    List<Key> results = run.getResults();
+    Map<Key, Entity> map = ds.get(results);
+    Set<String> modules = new HashSet<>();
+
+    for (Entry<Key, Entity> entry : map.entrySet()) {
+      BenchmarkResult result = new BenchmarkResult(entry.getValue());
+      modules.add(result.getBenchmarkName());
+    }
+
+    ArrayList<String> resultList = new ArrayList<>(modules);
+    Collections.sort(resultList);
+    return resultList;
+  }
+
+  public BenchmarkResultsTable getGraphs(String benchmarkName, int week, int year) {
+    Query query = BenchmarkGraph.createQuery(benchmarkName, week, year);
+    List<BenchmarkGraph> graphs = executeQuery(query);
+    return createResponse(benchmarkName, graphs, week, year);
+  }
+
   public void addBenchmarkResult(BenchmarkRunJson benchmarkRunJSON) throws ControllerException {
     ToPersist toPersist = createDomainObjects(benchmarkRunJSON);
     persistBenchmarkRun(toPersist, 3);
@@ -111,6 +153,62 @@
         benchmarkGraphData.commitIds, benchmarkGraphData.runsPerSecond);
   }
 
+  private BenchmarkResultsTable createResponse(String benchmarkName,
+      List<BenchmarkGraph> graphs, int week, int year) {
+    Collections.sort(graphs, new Comparator<BenchmarkGraph>() {
+
+      @Override
+      public int compare(BenchmarkGraph o1, BenchmarkGraph o2) {
+        return o1.getRunnerId().compareTo(o2.getRunnerId());
+      }
+    });
+    String weekName = String.format("Week: %d Year: %d", week, year);
+    List<String> allRunnerIds = new ArrayList<String>();
+
+    List<String> commitIds = new ArrayList<String>();
+    List<double[]> runnerResultList = new ArrayList<>();
+
+    boolean first = true;
+
+    for (BenchmarkGraph benchmarkGraph : graphs) {
+      if (first) {
+        commitIds.addAll(benchmarkGraph.getCommitIds());
+        first = false;
+      } else {
+        // if an update is in progress some graph entities might have been updated
+        // and some not. In this case we are just going to return an empty response
+        if (commitIds.size() != benchmarkGraph.getCommitIds().size()) {
+          return BenchmarkResultsTable.create(benchmarkName, weekName, year, week,
+              Collections.<String>emptyList(),Collections.<String>emptyList(),
+              Collections.<double[]>emptyList());
+        }
+      }
+
+      allRunnerIds.add(benchmarkGraph.getRunnerId());
+      Double[] runsPerSecond = benchmarkGraph.getRunsPerSecond().toArray(new Double[] {});
+      double[] runs = ArrayUtils.toPrimitive(runsPerSecond);
+      runnerResultList.add(runs);
+    }
+
+    return BenchmarkResultsTable.create(benchmarkName, weekName, year, week, commitIds,
+        allRunnerIds, runnerResultList);
+  }
+
+  private List<BenchmarkGraph> executeQuery(Query query) {
+    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
+    PreparedQuery prepare = ds.prepare(query);
+
+    List<Entity> entityList = prepare.asList(FetchOptions.Builder.withDefaults());
+
+    List<BenchmarkGraph> list = new ArrayList<>();
+    for (Entity entity : entityList) {
+      list.add(new BenchmarkGraph(entity));
+    }
+    return list;
+  }
+
+
+
   private BenchmarkGraphData calculateNewGraphData(String benchmarkName, String runnerId,
       long weekStartMsEpoch, long weekEndMsEpoch) {
 
@@ -209,7 +307,7 @@
         runnerIds.add(benchmarkResultJSON.getRunnerId());
         BenchmarkResult benchmarkResult = new BenchmarkResult(benchmarkRun.getKey(), moduleName,
             benchmarkResultJSON.getRunnerId());
-        benchmarkResult.setRunsPerMinute(benchmarkResultJSON.getRunsPerMinute());
+        benchmarkResult.setRunsPerSecond(benchmarkResultJSON.getRunsPerSecond());
         brToPersist.add(benchmarkResult);
       }
     }
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkProblem.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkProblem.java
deleted file mode 100644
index baabcd9..0000000
--- a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkProblem.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.dashboard.server.controller;
-
-/**
- * A benchmark problem indicates a regression in a benchmark.
- */
-public class BenchmarkProblem {
-  private String module;
-
-  private String text;
-
-  private String runnerId;
-
-  public BenchmarkProblem(String module, String runnerId, String text) {
-    this.module = module;
-    this.runnerId = runnerId;
-    this.text = text;
-  }
-
-  public String getModule() {
-    return module;
-  }
-
-  public String getText() {
-    return text;
-  }
-
-  public String getRunnerId() {
-    return runnerId;
-  }
-}
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/domain/BenchmarkGraph.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/domain/BenchmarkGraph.java
index a3bbab1..78ab75c 100644
--- a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/domain/BenchmarkGraph.java
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/domain/BenchmarkGraph.java
@@ -16,13 +16,16 @@
 import com.google.appengine.api.datastore.Entity;
 import com.google.appengine.api.datastore.Key;
 import com.google.appengine.api.datastore.KeyFactory;
+import com.google.appengine.api.datastore.Query;
+import com.google.appengine.api.datastore.Query.Filter;
+import com.google.appengine.api.datastore.Query.FilterOperator;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 /**
- * A BencharmGraph contains all results for a certain module and runner for one week.
+ * A BenchmarkGraph contains all results for a certain module and runner for one week.
  */
 public class BenchmarkGraph {
 
@@ -37,6 +40,20 @@
     return module + "_" + runnerId + "_" + week + "_" + year;
   }
 
+
+  public  static Query createQuery(String benchmarkName, int week, int year) {
+    Query query = new Query(BenchmarkGraph.NAME);
+    Filter moduleFilter = new Query.FilterPredicate("module", FilterOperator.EQUAL, benchmarkName);
+    Filter weekFilter = new Query.FilterPredicate("week", FilterOperator.EQUAL, week);
+    Filter yearFilter = new Query.FilterPredicate("year", FilterOperator.EQUAL, year);
+
+    Filter compositeFilter =
+        Query.CompositeFilterOperator.and(moduleFilter, weekFilter, yearFilter);
+
+    query.setFilter(compositeFilter);
+    return query;
+  }
+
   private Entity entity;
 
   /**
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/domain/BenchmarkResult.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/domain/BenchmarkResult.java
index 1b205b2..2621640 100644
--- a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/domain/BenchmarkResult.java
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/domain/BenchmarkResult.java
@@ -16,6 +16,8 @@
 import com.google.appengine.api.datastore.Entity;
 import com.google.appengine.api.datastore.Key;
 import com.google.appengine.api.datastore.KeyFactory;
+import com.google.appengine.api.datastore.Query;
+import com.google.appengine.api.datastore.Query.SortDirection;
 
 /**
  * A BenchmarkResult contains the runs per minute for one module on one runner.
@@ -31,6 +33,12 @@
     return benchmarkName + "&" + runnerId;
   }
 
+  public static Query createQueryLastest() {
+    Query query = new Query(BenchmarkRun.NAME);
+    query.addSort("commitTimeMsEpoch", SortDirection.DESCENDING);
+    return query;
+  }
+
   public static final String NAME = "BenchmarkResult";
 
   private Entity entity;
@@ -39,7 +47,7 @@
     entity = new Entity(createKey(run, benchmarkName, runnerId));
     entity.setProperty("runnerId", runnerId);
     entity.setProperty("benchmarkName", benchmarkName);
-    setRunsPerMinute(0);
+    setRunsPerSecond(0);
   }
 
   public BenchmarkResult(Entity entity) {
@@ -54,12 +62,12 @@
     return (String) entity.getProperty("runnerId");
   }
 
-  public void setRunsPerMinute(double runsPerMinute) {
-    entity.setProperty("runsPerMinute", runsPerMinute);
+  public void setRunsPerSecond(double runsPerMinute) {
+    entity.setProperty("runsPerSecond", runsPerMinute);
   }
 
   public double getRunsPerMinute() {
-    return (double) entity.getProperty("runsPerMinute");
+    return (double) entity.getProperty("runsPerSecond");
   }
 
   public Key getKey() {
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/guice/DashboardServletGuiceModule.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/guice/DashboardServletGuiceModule.java
index 9e8625e..fffc74c 100644
--- a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/guice/DashboardServletGuiceModule.java
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/guice/DashboardServletGuiceModule.java
@@ -13,10 +13,10 @@
  */
 package com.google.gwt.benchmark.dashboard.server.guice;
 
+import com.google.gwt.benchmark.dashboard.server.service.DashBoardServiceImpl;
 import com.google.gwt.benchmark.dashboard.server.servlets.AddBenchmarkResultServlet;
 import com.google.gwt.benchmark.dashboard.server.servlets.AuthServlet;
 import com.google.gwt.benchmark.dashboard.server.servlets.GraphUpdateWorkerServlet;
-import com.google.inject.Singleton;
 import com.google.inject.servlet.ServletModule;
 
 /**
@@ -28,11 +28,9 @@
 
   @Override
   protected void configureServlets() {
-    bind(AddBenchmarkResultServlet.class).in(Singleton.class);
     serve("/post_result").with(AddBenchmarkResultServlet.class);
-    bind(AuthServlet.class).in(Singleton.class);
     serve("/admin/").with(AuthServlet.class);
-    bind(GraphUpdateWorkerServlet.class).in(Singleton.class);
     serve(GRAPH_QUEUE_URL).with(GraphUpdateWorkerServlet.class);
+    serve("/dashboard/data/service").with(DashBoardServiceImpl.class);
   }
 }
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/service/DashBoardServiceImpl.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/service/DashBoardServiceImpl.java
new file mode 100644
index 0000000..ded8fe4
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/service/DashBoardServiceImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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.dashboard.server.service;
+
+import com.google.gwt.benchmark.common.shared.service.ServiceException;
+import com.google.gwt.benchmark.dashboard.server.controller.BenchmarkController;
+import com.google.gwt.benchmark.dashboard.server.controller.ControllerException;
+import com.google.gwt.benchmark.dashboard.shared.service.DashboardService;
+import com.google.gwt.benchmark.dashboard.shared.service.dto.BenchmarkResultsTable;
+import com.google.gwt.user.server.rpc.RemoteServiceServlet;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implementation for GWT service.
+ */
+@Singleton
+public class DashBoardServiceImpl extends RemoteServiceServlet implements DashboardService {
+
+  private static final Logger logger = Logger.getLogger(DashBoardServiceImpl.class.getName());
+
+  private BenchmarkController controller;
+
+  @Inject
+  public DashBoardServiceImpl(BenchmarkController controller) {
+    this.controller = controller;
+  }
+
+  @Override
+  public ArrayList<String> getLatestBenchmarkNames() throws ServiceException {
+    try {
+      return new ArrayList<>(controller.getLatestBenchmarkNames());
+    } catch (ControllerException e) {
+      logger.log(Level.WARNING, "Can not load modules", e);
+      throw new ServiceException("Can not load modules");
+    }
+  }
+
+  @Override
+  public BenchmarkResultsTable getLatestGraphs(String benchmarkName) throws ServiceException {
+    Calendar cal = Calendar.getInstance();
+    int week = cal.get(Calendar.WEEK_OF_YEAR);
+    int year = cal.get(Calendar.YEAR);
+    return controller.getGraphs(benchmarkName, week, year);
+  }
+
+  @Override
+  public BenchmarkResultsTable getGraphs(String benchmarkName, int week, int year)
+      throws ServiceException {
+      return controller.getGraphs(benchmarkName, week, year);
+  }
+}
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/AddBenchmarkResultServlet.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/AddBenchmarkResultServlet.java
index 082f5a5..b4d23b3 100644
--- a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/AddBenchmarkResultServlet.java
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/AddBenchmarkResultServlet.java
@@ -17,6 +17,7 @@
 import com.google.gwt.benchmark.common.shared.json.JsonFactory;
 import com.google.gwt.benchmark.dashboard.server.controller.AuthController;
 import com.google.gwt.benchmark.dashboard.server.controller.BenchmarkController;
+import com.google.inject.Singleton;
 import com.google.web.bindery.autobean.shared.AutoBean;
 import com.google.web.bindery.autobean.shared.AutoBeanCodex;
 
@@ -35,6 +36,7 @@
 /**
  * A put request on this servlet will add a new benchmark to the dashboard.
  */
+@Singleton
 public class AddBenchmarkResultServlet extends HttpServlet {
 
   private static final Logger logger = Logger.getLogger(AddBenchmarkResultServlet.class.getName());
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/AuthServlet.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/AuthServlet.java
index 4ac6978..f1541f6 100644
--- a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/AuthServlet.java
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/AuthServlet.java
@@ -16,6 +16,7 @@
 import com.google.gwt.benchmark.dashboard.server.controller.AuthController;
 import com.google.gwt.benchmark.dashboard.server.controller.ControllerException;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 import java.io.IOException;
 
@@ -27,6 +28,7 @@
 /**
  * This servlet can update the auth phrase required to post benchmarks.
  */
+@Singleton
 public class AuthServlet extends HttpServlet {
 
   private final AuthController authController;
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/GraphUpdateWorkerServlet.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/GraphUpdateWorkerServlet.java
index 220d2cb..42464a0 100644
--- a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/GraphUpdateWorkerServlet.java
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/server/servlets/GraphUpdateWorkerServlet.java
@@ -16,6 +16,7 @@
 import com.google.gwt.benchmark.dashboard.server.controller.BenchmarkController;
 import com.google.gwt.benchmark.dashboard.server.controller.ControllerException;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 import java.io.IOException;
 import java.util.logging.Level;
@@ -29,6 +30,7 @@
 /**
  * This servlet handles request for the graph update queue.
  */
+@Singleton
 public class GraphUpdateWorkerServlet extends HttpServlet {
 
   private static final Logger logger = Logger.getLogger(GraphUpdateWorkerServlet.class.getName());
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/shared/service/DashboardService.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/shared/service/DashboardService.java
new file mode 100644
index 0000000..abf9d9f
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/shared/service/DashboardService.java
@@ -0,0 +1,22 @@
+package com.google.gwt.benchmark.dashboard.shared.service;
+
+import com.google.gwt.benchmark.common.shared.service.ServiceException;
+import com.google.gwt.benchmark.dashboard.shared.service.dto.BenchmarkResultsTable;
+import com.google.gwt.user.client.rpc.RemoteService;
+import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
+
+import java.util.ArrayList;
+
+/**
+ * Service interface implemented server side.
+ */
+@RemoteServiceRelativePath("data/service")
+public interface DashboardService extends RemoteService {
+  ArrayList<String> getLatestBenchmarkNames() throws ServiceException;
+
+  BenchmarkResultsTable getLatestGraphs(String benchmarkName) throws ServiceException;
+
+  BenchmarkResultsTable getGraphs(String benchmarkName, int week, int year)
+      throws ServiceException;
+
+}
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/shared/service/DashboardServiceAsync.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/shared/service/DashboardServiceAsync.java
new file mode 100644
index 0000000..2e26209
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/shared/service/DashboardServiceAsync.java
@@ -0,0 +1,18 @@
+package com.google.gwt.benchmark.dashboard.shared.service;
+
+import com.google.gwt.benchmark.dashboard.shared.service.dto.BenchmarkResultsTable;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import java.util.ArrayList;
+
+/**
+ * Service interface to be injected into every object that needs data from the server.
+ */
+public interface DashboardServiceAsync {
+  void getLatestBenchmarkNames(AsyncCallback<ArrayList<String>> callback);
+
+  void getLatestGraphs(String module, AsyncCallback<BenchmarkResultsTable> callback);
+
+  void getGraphs(String module, int week, int year,
+      AsyncCallback<BenchmarkResultsTable> callback);
+}
diff --git a/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/shared/service/dto/BenchmarkResultsTable.java b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/shared/service/dto/BenchmarkResultsTable.java
new file mode 100644
index 0000000..b5421dc
--- /dev/null
+++ b/dashboard/src/main/java/com/google/gwt/benchmark/dashboard/shared/service/dto/BenchmarkResultsTable.java
@@ -0,0 +1,128 @@
+/*
+ * 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.dashboard.shared.service.dto;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.Lists;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * A table of results for one benchmark and one week.
+ * There is a row for each commit at which the benchmark was run, and a column for each variation
+ * of the benchmark.
+ * <p>
+ * The columns are in chronological order; the most recent is first. The rows are ordered by their
+ * corresponding runner ids.
+ */
+public class BenchmarkResultsTable implements Serializable {
+
+  public static BenchmarkResultsTable create(String benchmarkName, String weekName, int year,
+      int week, Collection<String> commitIds, Collection<String> runnerIds,
+      Collection<double[]> cellData) {
+    checkArgument(runnerIds.size() == cellData.size());
+    for (double[] row : cellData) {
+      checkArgument(row.length == commitIds.size());
+    }
+
+    BenchmarkResultsTable table = new BenchmarkResultsTable();
+    table.allRunnerIds = Lists.newArrayList(runnerIds);
+    table.benchmarkName = benchmarkName;
+    table.commitIds = Lists.newArrayList(commitIds);
+    table.runnerResultList = Lists.newArrayList(cellData);
+    table.weekName = weekName;
+    table.year = year;
+    table.week = week;
+    return table;
+  }
+
+  private ArrayList<double[]> runnerResultList;
+  private ArrayList<String> commitIds;
+
+  private ArrayList<String> allRunnerIds;
+  private String benchmarkName;
+
+  private String weekName;
+  private int year;
+
+  private int week;
+
+  protected BenchmarkResultsTable() {
+  }
+
+  public ArrayList<String> getCommitIds() {
+    return commitIds;
+  }
+
+  /** The name of the benchmark shown in this table */
+  public String getBenchmarkName() {
+    return benchmarkName;
+  }
+
+  /**
+   * The commit id for a column.
+   */
+  public String getCommitId(int columnIndex) {
+    return commitIds.get(columnIndex);
+  }
+
+  /**
+   * The runner id for a row.
+   */
+  public String getRunnerId(int rowIndex) {
+    return allRunnerIds.get(rowIndex);
+  }
+
+  /**
+   * The data for one cell in the table.
+   */
+  public double getRunsPerSecond(int rowIndex, int columnIndex) {
+    return runnerResultList.get(rowIndex)[columnIndex];
+  }
+
+  /**
+   * All runner ids for this benchmark in this week.
+   */
+  public ArrayList<String> getAllRunnerIds() {
+    return allRunnerIds;
+  }
+
+  public int getRowCount() {
+    return commitIds.size();
+  }
+
+  public int getColumnCount() {
+    return allRunnerIds.size();
+  }
+
+  /** The ISO-8601 week number. */
+  public int getWeek() {
+    return week;
+  }
+
+  /** The ISO-8601 week number. */
+  public int getYear() {
+    return year;
+  }
+
+  /**
+   * A user-friendly description of the week shown in this table.
+   */
+  public String getWeekName() {
+    return weekName;
+  }
+}
diff --git a/dashboard/src/main/webapp/index.html b/dashboard/src/main/webapp/index.html
new file mode 100644
index 0000000..f51fbba
--- /dev/null
+++ b/dashboard/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>GWT Benchmarks</title>
+<script type="text/javascript" src="dashboard/dashboard.nocache.js"></script>
+</head>
+<body>
+</body>
+</html>
\ No newline at end of file
diff --git a/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/client/ui/GraphCompositeTest.java b/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/client/ui/GraphCompositeTest.java
new file mode 100644
index 0000000..f26ae32
--- /dev/null
+++ b/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/client/ui/GraphCompositeTest.java
@@ -0,0 +1,346 @@
+/*
+ * 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.dashboard.client.ui;
+
+import com.google.gwt.benchmark.dashboard.shared.service.DashboardServiceAsync;
+import com.google.gwt.benchmark.dashboard.shared.service.dto.BenchmarkResultsTable;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.i18n.client.NumberFormat;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.visualization.client.AbstractDataTable.ColumnType;
+import com.google.gwt.visualization.client.DataTable;
+import com.google.gwt.visualization.client.visualizations.LineChart;
+import com.google.gwtmockito.GwtMockitoTestRunner;
+import com.google.inject.Provider;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+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 org.mockito.Mockito;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Test for {@link GraphComposite}.
+ */
+@RunWith(GwtMockitoTestRunner.class)
+public class GraphCompositeTest {
+
+  private static class CheckboxHolder {
+    CheckBox checkBox;
+    HandlerRegistration handlerRegistration;
+
+    public CheckboxHolder(CheckBox checkBox, HandlerRegistration handlerRegistration) {
+      this.checkBox = checkBox;
+      this.handlerRegistration = handlerRegistration;
+    }
+  }
+
+  private GraphComposite composite;
+
+  @Mock private DashboardServiceAsync service;
+  @Mock private NumberFormat numberFormat;
+  @Mock private Provider<CheckBox> checkBoxProvider;
+  @Mock private HistoryAccessor historyAccessor;
+  @Mock DataTable data;
+  @Mock LineChart.Options options;
+  @Mock ValueChangeEvent<Boolean> vce;
+
+  @Captor private ArgumentCaptor<AsyncCallback<BenchmarkResultsTable>> asyncCaptor;
+  @Captor private ArgumentCaptor<Runnable> runableCaptor;
+  @Captor private ArgumentCaptor<ValueChangeHandler<Boolean>> valueChangeHandlerCaptor;
+
+  @Before
+  public void setup() {
+    composite = new GraphComposite(service, checkBoxProvider, historyAccessor);
+  }
+
+  @Test
+  public void testStartWithNowAndNoRunners() {
+
+    when(historyAccessor.getToken()).thenReturn("!graph?benchmark=module1");
+
+    composite.start();
+    verify(composite.loadingLabel).setVisible(true);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.container).setVisible(false);
+    verify(composite.graphWidget).init(Mockito.<Runnable> anyObject());
+
+    verify(service).getLatestGraphs(eq("module1"), asyncCaptor.capture());
+  }
+
+  @Test
+  public void testStartWithOneRunners() {
+
+    when(historyAccessor.getToken()).thenReturn("!graph?benchmark=module1&w=4&y=2014&rids=[linux_ff]");
+
+    composite.start();
+    verify(composite.loadingLabel).setVisible(true);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.container).setVisible(false);
+    verify(composite.graphWidget).init(Mockito.<Runnable> anyObject());
+
+    verify(service).getGraphs(eq("module1"), eq(4), eq(2014), asyncCaptor.capture());
+  }
+
+  @Test
+  public void testStartWithNowAndTwoRunners() {
+
+    when(historyAccessor.getToken()).thenReturn("!graph?benchmark=module1&w=5&y=2014&rids=[linux_ff, linux_chrome]");
+
+    composite.start();
+    verify(composite.loadingLabel).setVisible(true);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.container).setVisible(false);
+    verify(composite.graphWidget).init(Mockito.<Runnable> anyObject());
+
+    verify(service).getGraphs(eq("module1"), eq(5), eq(2014), asyncCaptor.capture());
+  }
+
+  @Test
+  public void testRenderData() {
+
+    when(historyAccessor.getToken()).thenReturn("!graph?benchmark=module1&w=5&y=2014&rids=[linux_ff, linux_chrome]");
+
+    composite.start();
+    verify(composite.loadingLabel).setVisible(true);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.container).setVisible(false);
+    verify(composite.graphWidget).init(runableCaptor.capture());
+    verify(service).getGraphs(eq("module1"), eq(5), eq(2014), asyncCaptor.capture());
+
+    verifyNoMoreInteractions(composite.container);
+
+    BenchmarkResultsTable result = BenchmarkResultsTable.create("module1", "someWeekName1", 2014, 5,
+        Arrays.asList("commit1", "commit2", "commit3"),
+        Arrays.asList("linux_ff", "linux_chrome", "win_ie11"),
+        Arrays.asList(new double[] {1, 2, 3}, new double[] {4, 5, 6}, new double[] {7, 8, 9}));
+
+    CheckboxHolder holder1 = createMockedCheckBox();
+    CheckboxHolder holder2 = createMockedCheckBox();
+    CheckboxHolder holder3 = createMockedCheckBox();
+
+    when(checkBoxProvider.get()).thenReturn(holder1.checkBox, holder2.checkBox, holder3.checkBox);
+
+    when(composite.graphWidget.createData()).thenReturn(data);
+    when(composite.graphWidget.createOptions()).thenReturn(options);
+
+    // Test that we do not render before the visualization api is loaded
+    asyncCaptor.getValue().onSuccess(result);
+    verifyNoMoreInteractions(composite.container);
+
+    runableCaptor.getValue().run();
+
+    verify(composite.loadingLabel).setVisible(false);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.container).setVisible(true);
+
+    verify(holder1.checkBox).setText("linux_ff");
+    verify(holder2.checkBox).setText("linux_chrome");
+    verify(holder3.checkBox).setText("win_ie11");
+
+    verify(holder1.checkBox).setValue(true);
+    verify(holder2.checkBox).setValue(true);
+    verify(holder3.checkBox).setValue(false);
+
+    verify(holder1.checkBox).addValueChangeHandler(valueChangeHandlerCaptor.capture());
+
+    verify(composite.checkBoxContainer).add(holder1.checkBox);
+    verify(composite.checkBoxContainer).add(holder2.checkBox);
+    verify(composite.checkBoxContainer).add(holder3.checkBox);
+
+    verify(data).addColumn(ColumnType.STRING, "Commits");
+    verify(data).addColumn(ColumnType.NUMBER, "linux_ff");
+    verify(data).addColumn(ColumnType.NUMBER, "linux_chrome");
+
+    verify(data).addRows(3);
+
+    verify(data).setValue(0, 0, "commit1");
+    verify(data).setValue(1, 0, "commit2");
+    verify(data).setValue(2, 0, "commit3");
+
+    verify(data).setValue(0, 1, 1.0);
+    verify(data).setValue(1, 1, 2.0);
+    verify(data).setValue(2, 1, 3.0);
+
+    verify(data).setValue(0, 2, 4.0);
+    verify(data).setValue(1, 2, 5.0);
+    verify(data).setValue(2, 2, 6.0);
+
+    verify(options).setWidth(800);
+    verify(options).setHeight(600);
+    verify(options).setTitle("module1");
+
+    verify(composite.graphWidget).createData();
+    verify(composite.graphWidget).createOptions();
+    verify(composite.graphWidget).displayChart(options, data);
+
+    verifyNoMoreInteractions(composite.graphWidget, data, options);
+
+    // change a CheckBox
+    when(vce.getValue()).thenReturn(false);
+    valueChangeHandlerCaptor.getValue().onValueChange(vce);
+
+    verify(holder1.handlerRegistration).removeHandler();
+    verify(holder2.handlerRegistration).removeHandler();
+    verify(holder3.handlerRegistration).removeHandler();
+
+    verify(service).getGraphs(eq("module1"), eq(5), eq(2014), asyncCaptor.capture());
+    verify(historyAccessor).newItem("!graph?benchmark=module1&w=5&y=2014&rids=[linux_chrome]", false);
+  }
+
+  @Test
+  public void testRenderDataEmptyDataSet() {
+
+    when(historyAccessor.getToken()).thenReturn("!graph?benchmark=module1&w=5&y=2014&rids=[linux_ff, linux_chrome]");
+
+    composite.start();
+    verify(composite.loadingLabel).setVisible(true);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.container).setVisible(false);
+    verify(composite.graphWidget).init(runableCaptor.capture());
+    verify(service).getGraphs(eq("module1"), eq(5), eq(2014), asyncCaptor.capture());
+
+    verifyNoMoreInteractions(composite.container);
+
+    BenchmarkResultsTable result = BenchmarkResultsTable.create("module1", "someWeekName1", 2014, 5,
+        Collections.<String>emptyList(),
+        Collections.<String>emptyList(),
+        Collections.<double[]>emptyList());
+
+    runableCaptor.getValue().run();
+    asyncCaptor.getValue().onSuccess(result);
+
+    verify(composite.checkBoxContainer).clear();
+    verify(composite.container).setVisible(true);
+    verify(composite.weekLabel).setText("someWeekName1");
+
+    verifyNoMoreInteractions(checkBoxProvider);
+
+    verify(composite.graphWidget).clear();
+    verifyNoMoreInteractions(composite.graphWidget);
+  }
+
+  @Test
+  public void testForwardButton() {
+
+    when(historyAccessor.getToken()).thenReturn("!graph?benchmark=module1&w=5&y=2014&rids=[linux_ff, linux_chrome]");
+
+    composite.start();
+    verify(composite.loadingLabel).setVisible(true);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.container).setVisible(false);
+    verify(composite.graphWidget).init(runableCaptor.capture());
+    verify(service).getGraphs(eq("module1"), eq(5), eq(2014), asyncCaptor.capture());
+
+    verifyNoMoreInteractions(composite.container);
+
+    BenchmarkResultsTable result = BenchmarkResultsTable.create("module1", "someWeek1", 2014, 5,
+        Arrays.asList("commit1", "commit2", "commit3"),
+        Arrays.asList("linux_ff", "linux_chrome", "win_ie11"),
+        Arrays.asList(new double[] {1, 2, 3}, new double[] {4, 5, 6}, new double[] {7, 8, 9}));
+
+    CheckboxHolder holder1 = createMockedCheckBox();
+    CheckboxHolder holder2 = createMockedCheckBox();
+    CheckboxHolder holder3 = createMockedCheckBox();
+    CheckboxHolder holder4 = createMockedCheckBox();
+    CheckboxHolder holder5 = createMockedCheckBox();
+    CheckboxHolder holder6 = createMockedCheckBox();
+
+    when(checkBoxProvider.get()).thenReturn(holder1.checkBox, holder2.checkBox, holder3.checkBox,
+        holder4.checkBox, holder5.checkBox, holder6.checkBox);
+
+    when(composite.graphWidget.createData()).thenReturn(data);
+    when(composite.graphWidget.createOptions()).thenReturn(options);
+
+    // Test that we do not render before the visualization api is loaded
+    asyncCaptor.getValue().onSuccess(result);
+    verifyNoMoreInteractions(composite.container);
+
+    runableCaptor.getValue().run();
+    verify(composite.loadingLabel).setVisible(false);
+
+
+    reset(composite.loadingLabel, composite.errorLabel, composite.container);
+
+    // press the forward button
+    composite.onForwardButtonClicked(null);
+    verify(historyAccessor).newItem(
+        "!graph?benchmark=module1&w=6&y=2014&rids=[linux_chrome, linux_ff]", false);
+
+    verify(composite.loadingLabel).setVisible(true);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.container).setVisible(false);
+    verify(service).getGraphs(eq("module1"), eq(6), eq(2014), asyncCaptor.capture());
+
+
+    result = BenchmarkResultsTable.create("module1", "someWeek1", 2014, 6,
+        Arrays.asList("commit1", "commit2", "commit3"),
+        Arrays.asList("linux_ff", "linux_chrome", "win_ie11"),
+        Arrays.asList(new double[] {1, 2, 3}, new double[] {4, 5, 6}, new double[] {7, 8, 9}));
+
+    asyncCaptor.getValue().onSuccess(result);
+
+    verify(holder1.handlerRegistration).removeHandler();
+    verify(holder2.handlerRegistration).removeHandler();
+    verify(holder3.handlerRegistration).removeHandler();
+    reset(composite.loadingLabel, composite.errorLabel, composite.container, service);
+
+    // press the back button
+    composite.onBackButtonClicked(null);
+
+    verify(historyAccessor).newItem(
+        "!graph?benchmark=module1&w=5&y=2014&rids=[linux_chrome, linux_ff]", false);
+
+    verify(composite.loadingLabel).setVisible(true);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.container).setVisible(false);
+    verify(service).getGraphs(eq("module1"), eq(5), eq(2014), asyncCaptor.capture());
+
+
+    result = BenchmarkResultsTable.create("module1", "someWeek1", 2014, 6,
+        Arrays.asList("commit1", "commit2", "commit3"),
+        Arrays.asList("linux_ff", "linux_chrome", "win_ie11"),
+        Arrays.asList(new double[] {1, 2, 3}, new double[] {4, 5, 6}, new double[] {7, 8, 9}));
+
+    asyncCaptor.getValue().onSuccess(result);
+
+    verify(holder1.handlerRegistration).removeHandler();
+    verify(holder2.handlerRegistration).removeHandler();
+    verify(holder3.handlerRegistration).removeHandler();
+  }
+
+  private CheckboxHolder createMockedCheckBox() {
+    CheckBox checkBox = mock(CheckBox.class);
+    HandlerRegistration hr = mock(HandlerRegistration.class);
+    when(checkBox.addValueChangeHandler(Mockito.<ValueChangeHandler<Boolean>> anyObject()))
+    .thenReturn(hr);
+    when(checkBoxProvider.get()).thenReturn(checkBox);
+    return new CheckboxHolder(checkBox, hr);
+  }
+}
diff --git a/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/client/ui/ModuleOverviewCompositeTest.java b/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/client/ui/ModuleOverviewCompositeTest.java
new file mode 100644
index 0000000..2b70ba5
--- /dev/null
+++ b/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/client/ui/ModuleOverviewCompositeTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.dashboard.client.ui;
+
+import com.google.gwt.benchmark.dashboard.shared.service.DashboardServiceAsync;
+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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+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;
+
+/**
+ * Test for {@link ModuleOverviewComposite}.
+ */
+@RunWith(GwtMockitoTestRunner.class)
+public class ModuleOverviewCompositeTest {
+
+  private ModuleOverviewComposite composite;
+
+  @Mock private DashboardServiceAsync service;
+  @Mock private NumberFormat numberFormat;
+  @Mock private Provider<Label> labelProvider;
+  @Mock HistoryAccessor historyAccessor;
+
+  @Captor private ArgumentCaptor<AsyncCallback<ArrayList<String>>> asyncCaptor;
+  @Captor private ArgumentCaptor<ClickHandler> clickHandlerCaptor1;
+  @Captor private ArgumentCaptor<ClickHandler> clickHandlerCaptor2;
+
+  @Before
+  public void setup() {
+    composite = new ModuleOverviewComposite(service, labelProvider, historyAccessor);
+  }
+
+  @Test
+  public void testPresenterAddsViewAndInitializedView() {
+    composite.start();
+    verify(composite.loadingLabel).setVisible(true);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.contentContainer).setVisible(false);
+  }
+
+  @Test
+  public void testRenderDataWhileServerIsNotRunning() {
+    composite.start();
+
+    verify(composite.loadingLabel).setVisible(true);
+    verify(composite.errorLabel).setVisible(false);
+    verify(composite.contentContainer).setVisible(false);
+
+    Label label1 = mock(Label.class);
+    Label label2 = mock(Label.class);
+    when(labelProvider.get()).thenReturn(label1, label2);
+
+    verify(service).getLatestBenchmarkNames(asyncCaptor.capture());
+
+    AsyncCallback<ArrayList<String>> asyncCallback = asyncCaptor.getValue();
+    asyncCallback.onSuccess(new ArrayList<>(Arrays.asList("module1", "module2")));
+
+    verify(composite.loadingLabel).setVisible(false);
+    verify(composite.contentContainer).setVisible(true);
+    verify(composite.contentContainer).clear();
+    verify(composite.contentContainer).add(label1);
+    verify(composite.contentContainer).add(label2);
+
+    verify(label1).setText("module1");
+    verify(label1).addClickHandler(clickHandlerCaptor1.capture());
+    verify(label2).setText("module2");
+    verify(label2).addClickHandler(clickHandlerCaptor2.capture());
+
+    verifyNoMoreInteractions(composite.contentContainer, composite.errorLabel,
+     composite.loadingLabel, label1, label2);
+
+    clickHandlerCaptor1.getValue().onClick(null);
+    verify(historyAccessor).newItem("!graph?benchmark=module1", true);
+
+    clickHandlerCaptor2.getValue().onClick(null);
+    verify(historyAccessor).newItem("!graph?benchmark=module2", true);
+  }
+}
diff --git a/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkControllerTest.java b/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkControllerTest.java
index c248bbd..ab23955 100644
--- a/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkControllerTest.java
+++ b/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/server/controller/BenchmarkControllerTest.java
@@ -32,6 +32,7 @@
 import com.google.gwt.benchmark.dashboard.server.domain.BenchmarkResult;
 import com.google.gwt.benchmark.dashboard.server.domain.BenchmarkRun;
 import com.google.gwt.benchmark.dashboard.server.servlets.AddBenchmarkResultServletTest;
+import com.google.gwt.benchmark.dashboard.shared.service.dto.BenchmarkResultsTable;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -149,19 +150,19 @@
     benchmarkRun.setRunnerIds(runnerIds);
     BenchmarkResult benchmarkResult1 =
         new BenchmarkResult(benchmarkRun.getKey(), "module1", "linux_ff");
-    benchmarkResult1.setRunsPerMinute(100);
+    benchmarkResult1.setRunsPerSecond(100);
     ds.put(benchmarkResult1.getEntity());
     BenchmarkResult benchmarkResult2 =
         new BenchmarkResult(benchmarkRun.getKey(), "module1", "linux_chrome");
-    benchmarkResult2.setRunsPerMinute(200);
+    benchmarkResult2.setRunsPerSecond(200);
     ds.put(benchmarkResult2.getEntity());
     BenchmarkResult benchmarkResult3 =
         new BenchmarkResult(benchmarkRun.getKey(), "module2", "linux_ff");
-    benchmarkResult3.setRunsPerMinute(300);
+    benchmarkResult3.setRunsPerSecond(300);
     ds.put(benchmarkResult3.getEntity());
     BenchmarkResult benchmarkResult4 =
         new BenchmarkResult(benchmarkRun.getKey(), "module2", "linux_chrome");
-    benchmarkResult4.setRunsPerMinute(400);
+    benchmarkResult4.setRunsPerSecond(400);
     ds.put(benchmarkResult4.getEntity());
 
     benchmarkRun.setResults(Arrays.asList(benchmarkResult1.getKey(), benchmarkResult2.getKey(),
@@ -173,16 +174,16 @@
     benchmarkRun = new BenchmarkRun("commitId1", commitTime_20th_march_2014 + 5000L);
     benchmarkRun.setRunnerIds(runnerIds);
     benchmarkResult1 = new BenchmarkResult(benchmarkRun.getKey(), "module1", "linux_ff");
-    benchmarkResult1.setRunsPerMinute(101);
+    benchmarkResult1.setRunsPerSecond(101);
     ds.put(benchmarkResult1.getEntity());
     benchmarkResult2 = new BenchmarkResult(benchmarkRun.getKey(), "module1", "linux_chrome");
-    benchmarkResult2.setRunsPerMinute(201);
+    benchmarkResult2.setRunsPerSecond(201);
     ds.put(benchmarkResult2.getEntity());
     benchmarkResult3 = new BenchmarkResult(benchmarkRun.getKey(), "module2", "linux_ff");
-    benchmarkResult3.setRunsPerMinute(301);
+    benchmarkResult3.setRunsPerSecond(301);
     ds.put(benchmarkResult3.getEntity());
     benchmarkResult4 = new BenchmarkResult(benchmarkRun.getKey(), "module2", "linux_chrome");
-    benchmarkResult4.setRunsPerMinute(401);
+    benchmarkResult4.setRunsPerSecond(401);
     ds.put(benchmarkResult4.getEntity());
 
     benchmarkRun.setResults(Arrays.asList(benchmarkResult1.getKey(), benchmarkResult2.getKey(),
@@ -194,16 +195,16 @@
     benchmarkRun = new BenchmarkRun("commitId2", commitTime_20th_march_2014 - oneWeekInMs);
     benchmarkRun.setRunnerIds(runnerIds);
     benchmarkResult1 = new BenchmarkResult(benchmarkRun.getKey(), "module1", "linux_ff");
-    benchmarkResult1.setRunsPerMinute(102);
+    benchmarkResult1.setRunsPerSecond(102);
     ds.put(benchmarkResult1.getEntity());
     benchmarkResult2 = new BenchmarkResult(benchmarkRun.getKey(), "module1", "linux_chrome");
-    benchmarkResult2.setRunsPerMinute(202);
+    benchmarkResult2.setRunsPerSecond(202);
     ds.put(benchmarkResult2.getEntity());
     benchmarkResult3 = new BenchmarkResult(benchmarkRun.getKey(), "module2", "linux_ff");
-    benchmarkResult3.setRunsPerMinute(302);
+    benchmarkResult3.setRunsPerSecond(302);
     ds.put(benchmarkResult3.getEntity());
     benchmarkResult4 = new BenchmarkResult(benchmarkRun.getKey(), "module2", "linux_chrome");
-    benchmarkResult4.setRunsPerMinute(402);
+    benchmarkResult4.setRunsPerSecond(402);
     ds.put(benchmarkResult4.getEntity());
 
     benchmarkRun.setResults(Arrays.asList(benchmarkResult1.getKey(), benchmarkResult2.getKey(),
@@ -215,16 +216,16 @@
     benchmarkRun = new BenchmarkRun("commitId3", commitTime_20th_march_2014 + oneWeekInMs);
     benchmarkRun.setRunnerIds(runnerIds);
     benchmarkResult1 = new BenchmarkResult(benchmarkRun.getKey(), "module1", "linux_ff");
-    benchmarkResult1.setRunsPerMinute(103);
+    benchmarkResult1.setRunsPerSecond(103);
     ds.put(benchmarkResult1.getEntity());
     benchmarkResult2 = new BenchmarkResult(benchmarkRun.getKey(), "module1", "linux_chrome");
-    benchmarkResult2.setRunsPerMinute(203);
+    benchmarkResult2.setRunsPerSecond(203);
     ds.put(benchmarkResult2.getEntity());
     benchmarkResult3 = new BenchmarkResult(benchmarkRun.getKey(), "module2", "linux_ff");
-    benchmarkResult3.setRunsPerMinute(303);
+    benchmarkResult3.setRunsPerSecond(303);
     ds.put(benchmarkResult3.getEntity());
     benchmarkResult4 = new BenchmarkResult(benchmarkRun.getKey(), "module2", "linux_chrome");
-    benchmarkResult4.setRunsPerMinute(403);
+    benchmarkResult4.setRunsPerSecond(403);
     ds.put(benchmarkResult4.getEntity());
 
     benchmarkRun.setResults(Arrays.asList(benchmarkResult1.getKey(), benchmarkResult2.getKey(),
@@ -318,4 +319,47 @@
     // delete the entity
     ds.delete(benchmarkGraph.getEntity().getKey());
   }
+
+  @Test
+  public void testGetGraph() {
+
+    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
+
+    // put some graphs in the store
+    BenchmarkGraph benchmarkGraph1 = new BenchmarkGraph("module1", "linux_ff", 4, 2014);
+    benchmarkGraph1.setCommitIds(Arrays.asList("commit1", "commit2", "commit3"));
+    benchmarkGraph1.setRunsPerSecond(Arrays.asList(1.0, 2.0, 3.0));
+    ds.put(benchmarkGraph1.getEntity());
+
+    BenchmarkGraph benchmarkGraph2 = new BenchmarkGraph("module1", "linux_chrome", 4, 2014);
+    benchmarkGraph2.setCommitIds(Arrays.asList("commit1", "commit2", "commit3"));
+    benchmarkGraph2.setRunsPerSecond(Arrays.asList(4.0, 5.0, 6.0));
+    ds.put(benchmarkGraph2.getEntity());
+
+    // another week
+    BenchmarkGraph benchmarkGraph3 = new BenchmarkGraph("module1", "linux_chrome", 5, 2014);
+    benchmarkGraph3.setCommitIds(Arrays.asList("commit4", "commit5", "commit6"));
+    benchmarkGraph3.setRunsPerSecond(Arrays.asList(4.1, 5.1, 6.1));
+    ds.put(benchmarkGraph3.getEntity());
+
+    BenchmarkController controller = new BenchmarkController();
+    BenchmarkResultsTable dto =
+        controller.getGraphs("module1", 4, 2014);
+
+    Assert.assertEquals(2, dto.getColumnCount());
+    Assert.assertEquals(2, dto.getAllRunnerIds().size());
+    Assert.assertTrue(dto.getAllRunnerIds().contains("linux_chrome"));
+    Assert.assertTrue(dto.getAllRunnerIds().contains("linux_ff"));
+
+    Assert.assertEquals(3, dto.getRowCount());
+    Assert.assertEquals(Arrays.asList("commit1", "commit2", "commit3"), dto.getCommitIds());
+    Assert.assertEquals("module1", dto.getBenchmarkName());
+
+    Assert.assertEquals(4.0, dto.getRunsPerSecond(0, 0), 0.0001);
+    Assert.assertEquals(5.0, dto.getRunsPerSecond(0, 1), 0.0001);
+    Assert.assertEquals(6.0, dto.getRunsPerSecond(0, 2), 0.0001);
+    Assert.assertEquals(1.0, dto.getRunsPerSecond(1, 0), 0.0001);
+    Assert.assertEquals(2.0, dto.getRunsPerSecond(1, 1), 0.0001);
+    Assert.assertEquals(3.0, dto.getRunsPerSecond(1, 2), 0.0001);
+  }
 }
diff --git a/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/server/servlets/AddBenchmarkResultServletTest.java b/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/server/servlets/AddBenchmarkResultServletTest.java
index be24c90..9b99fed 100644
--- a/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/server/servlets/AddBenchmarkResultServletTest.java
+++ b/dashboard/src/test/java/com/google/gwt/benchmark/dashboard/server/servlets/AddBenchmarkResultServletTest.java
@@ -70,13 +70,13 @@
     BenchmarkResultJson resultJSON = factory.result().as();
     resultJSON.setBenchmarkName(moduleName);
     resultJSON.setRunnerId("firefox_linux");
-    resultJSON.setRunsPerMinute(3);
+    resultJSON.setRunsPerSecond(3);
     list.add(resultJSON);
 
     BenchmarkResultJson resultJSON1 = factory.result().as();
     resultJSON1.setBenchmarkName(moduleName);
     resultJSON1.setRunnerId("chrome_linux");
-    resultJSON1.setRunsPerMinute(4);
+    resultJSON1.setRunsPerSecond(4);
     list.add(resultJSON1);
 
     String moduleName1 = "module2";
@@ -86,12 +86,12 @@
     BenchmarkResultJson resultJSON2 = factory.result().as();
     resultJSON2.setBenchmarkName(moduleName1);
     resultJSON2.setRunnerId("firefox_linux");
-    resultJSON2.setRunsPerMinute(3);
+    resultJSON2.setRunsPerSecond(3);
     list1.add(resultJSON2);
     BenchmarkResultJson resultJSON3 = factory.result().as();
     resultJSON3.setBenchmarkName(moduleName1);
     resultJSON3.setRunnerId("chrome_linux");
-    resultJSON3.setRunsPerMinute(4);
+    resultJSON3.setRunsPerSecond(4);
     list1.add(resultJSON3);
 
     runJSON.setResultByBenchmarkName(results);