Merge "Suppport profiling on target side."
diff --git a/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java b/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java
index 3442884..9b1e123 100644
--- a/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java
+++ b/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java
@@ -72,6 +72,7 @@
     static final String SERIAL = "serial";
     static final String TEST_SUITE = "test_suite";
     static final String ABI_BITNESS = "abi_bitness";
+    static final String RUN_32BIT_ON_64BIT_ABI = "run_32bit_on_64bit_abi";
     static final String VTS = "vts";
     static final String CONFIG_FILE_EXTENSION = ".config";
     static final String INCLUDE_FILTER = "include_filter";
@@ -125,6 +126,10 @@
             isTimeVal = true)
     private long mRuntimeHint = 60000;  // 1 minute
 
+    @Option(name = "run-32bit-on-64bit-abi",
+            description = "Whether to run 32bit tests on 64bit abi.")
+    private boolean mRun32bBitOn64BitAbi = false;
+
     @Option(name = "binary-test-sources",
             description = "Binary test source paths relative to vts testcase directory on host."
                     + "Format of tags:"
@@ -483,10 +488,15 @@
         CLog.i("Added exclude filter to test suite: %s", mExcludeFilters);
         jsonObject.put(TEST_SUITE, suite);
         CLog.i("Added %s to the Json object", TEST_SUITE);
-        if (mAbi != null){
+
+        if (mAbi != null) {
             jsonObject.put(ABI_BITNESS, mAbi.getBitness());
             CLog.i("Added %s to the Json object", ABI_BITNESS);
         }
+        if (mRun32bBitOn64BitAbi) {
+            jsonObject.put(RUN_32BIT_ON_64BIT_ABI, mRun32bBitOn64BitAbi);
+            CLog.i("Added %s to the Json object", RUN_32BIT_ON_64BIT_ABI);
+        }
 
         if (!mBinaryTestSources.isEmpty()) {
             jsonObject.put(BINARY_TEST_SOURCES, new JSONArray(mBinaryTestSources));
diff --git a/runners/host/base_test.py b/runners/host/base_test.py
index 32f5221..52cba68 100644
--- a/runners/host/base_test.py
+++ b/runners/host/base_test.py
@@ -87,6 +87,8 @@
 
         # Set abi bitness (optional)
         self.abi_bitness = self.getUserParam(keys.ConfigKeys.IKEY_ABI_BITNESS)
+        self.run_32bit_on_64bit_abi = self.getUserParam(
+            keys.ConfigKeys.IKEY_RUN_32BIT_ON_64BIT_ABI, default_value=False)
 
     def __enter__(self):
         return self
@@ -130,7 +132,10 @@
             else:
                 setattr(self, name, self.user_params[name])
 
-    def getUserParam(self, param_name, error_if_not_found=False, default_value=None):
+    def getUserParam(self,
+                     param_name,
+                     error_if_not_found=False,
+                     default_value=None):
         """Get the value of a single user parameter.
 
         This method returns the value of specified user parameter.
@@ -374,6 +379,8 @@
         will be skipped. By our convention, this function will look for bitness in suffix
         formated as "32bit", "32Bit", "32BIT", or 64 bit equivalents.
 
+        This method assumes  const.SUFFIX_32BIT and const.SUFFIX_64BIT are in lower cases.
+
         Args:
             test_name: string, name of a test
 
@@ -399,7 +406,8 @@
                 (test_name[-len(const.SUFFIX_32BIT):].lower() ==
                  const.SUFFIX_32BIT and self.abi_bitness != "32") or
                 (test_name[-len(const.SUFFIX_64BIT):].lower() ==
-                 const.SUFFIX_64BIT and self.abi_bitness != "64"),
+                 const.SUFFIX_64BIT and self.abi_bitness != "64" and
+                 not self.run_32bit_on_64bit_abi),
                 "Test case '{}' excluded as abi bitness is {}.".format(
                     test_name, self.abi_bitness))
 
diff --git a/runners/host/const.py b/runners/host/const.py
index cd290d3..d67692b 100644
--- a/runners/host/const.py
+++ b/runners/host/const.py
@@ -22,5 +22,7 @@
 
 LIST_ITEM_DELIMITER = ','
 
+# Note: filterOneTest method in base_test.py assumes SUFFIX_32BIT and SUFFIX_64BIT
+# are in lower cases.
 SUFFIX_32BIT = "32bit"
 SUFFIX_64BIT = "64bit"
\ No newline at end of file
diff --git a/runners/host/keys.py b/runners/host/keys.py
index 2874efd..48cfa68 100644
--- a/runners/host/keys.py
+++ b/runners/host/keys.py
@@ -47,6 +47,7 @@
     IKEY_TESTBED_NAME = "testbed_name"
     IKEY_LOG_PATH = "log_path"
     IKEY_ABI_BITNESS = "abi_bitness"
+    IKEY_RUN_32BIT_ON_64BIT_ABI = "run_32bit_on_64bit_abi"
 
     IKEY_BUILD = "build"
     IKEY_DATA_FILE_PATH = "data_file_path"
diff --git a/runners/host/tcp_client/vts_tcp_client.py b/runners/host/tcp_client/vts_tcp_client.py
index 2ab514c..e79b9e0 100755
--- a/runners/host/tcp_client/vts_tcp_client.py
+++ b/runners/host/tcp_client/vts_tcp_client.py
@@ -185,19 +185,28 @@
                 return mirror_object.MirrorObject(
                      self, result.return_type_submodule_spec, None)
             if len(result.return_type_hidl) == 1:
+                if hasattr(result, "raw_coverage_data"):
+                    has_coverage = True
+                result_scalar = None
                 if (result.return_type_hidl[0].type ==
                     CompSpecMsg_pb2.TYPE_SCALAR):
-                    return getattr(result.return_type_hidl[0].scalar_value,
-                                   result.return_type_hidl[0].scalar_type)
+                    result_scalar =  getattr(
+                        result.return_type_hidl[0].scalar_value,
+                        result.return_type_hidl[0].scalar_type)
                 elif (result.return_type_hidl[0].type ==
-                    CompSpecMsg_pb2.TYPE_ENUM):
+                      CompSpecMsg_pb2.TYPE_ENUM):
                     scalar_type = getattr(result.return_type_hidl[0],
                                           "scalar_type", "")
                     if scalar_type:
-                        return getattr(result.return_type_hidl[0].scalar_value,
-                                       scalar_type)
+                        result_scalar = getattr(
+                            result.return_type_hidl[0].scalar_value,
+                            scalar_type)
                     else:
-                        return result.return_type_hidl[0].scalar_value.int32_t
+                        result_scalar = result.return_type_hidl[0].scalar_value.int32_t
+                if not has_coverage:
+                    return result_scalar
+                else:
+                    return result_scalar, {"coverage": result.raw_coverage_data}
             return result
         logging.error("NOTICE - Likely a crash discovery!")
         logging.error("SysMsg_pb2.SUCCESS is %s", SysMsg_pb2.SUCCESS)
diff --git a/sysfuzzer/common/fuzz_tester/FuzzerBase.cpp b/sysfuzzer/common/fuzz_tester/FuzzerBase.cpp
index 78db922..9619597 100644
--- a/sysfuzzer/common/fuzz_tester/FuzzerBase.cpp
+++ b/sysfuzzer/common/fuzz_tester/FuzzerBase.cpp
@@ -261,9 +261,13 @@
 
 FuzzerBase::~FuzzerBase() { free(component_filename_); }
 
-void wfn() { cout << "wfn" << endl; }
+void wfn() {
+  cout << __func__ << endl;
+}
 
-void ffn() { cout << "ffn" << endl; }
+void ffn() {
+  cout << __func__ << endl;
+}
 
 bool FuzzerBase::LoadTargetComponent(const char* target_dll_path) {
   cout << __func__ << ":" << __LINE__ << " entry" << endl;
@@ -291,10 +295,11 @@
 #if SANCOV
   cout << __FUNCTION__ << "sancov reset "
        << target_loader_.SancovResetCoverage() << endl;
-  ;
 #endif
 
   if (target_dll_path_) {
+    cout << __func__ << ":" << __LINE__ << " target DLL path "
+         << target_dll_path_ << endl;
     string target_path(target_dll_path_);
 
     size_t offset = target_path.rfind("/", target_path.length());
@@ -463,25 +468,89 @@
   cout << __func__ << ":" << __LINE__ << " end" << endl;
 }
 
-bool FuzzerBase::FunctionCallEnd(FunctionSpecificationMessage* msg) {
-  cout << __FUNCTION__ << ": gcov flush " << endl;
-  cout << __func__ << endl;
-#if USE_GCOV
-  target_loader_.GcovFlush();
-  // find the file.
-  if (!gcov_output_basepath_) {
-    cerr << __FUNCTION__ << ": no gcov basepath set" << endl;
-    return false;
+const string default_gcov_output_basepath = "/data/local/tmp";
+
+bool FuzzerBase::ReadGcdaFile(
+    const string& basepath, const string& filename,
+    FunctionSpecificationMessage* msg) {
+#if VTS_GCOV_DEBUG
+      cout << __func__ << ":" << __LINE__
+           << " file = " << dent->d_name << endl;
+#endif
+  if (string(filename).rfind(".gcda") != string::npos) {
+    string buffer = basepath + "/" + filename;
+    vector<unsigned>* processed_data = android::vts::parse_gcda_file(
+        buffer.c_str());
+    for (const auto& data : *processed_data) {
+      msg->mutable_processed_coverage_data()->Add(data);
+    }
+
+    FILE* gcda_file = fopen(buffer.c_str(), "rb");
+    if (!gcda_file) {
+      cerr << __func__ << ":" << __LINE__
+           << " Unable to open a gcda file. " << buffer << endl;
+    } else {
+      cout << __func__ << ":" << __LINE__
+           << " Opened a gcda file. " << buffer << endl;
+      fseek(gcda_file, 0, SEEK_END);
+      long gcda_file_size = ftell(gcda_file);
+#if VTS_GCOV_DEBUG
+      cout << __func__ << ":" << __LINE__
+           << " File size " << gcda_file_size << " bytes" << endl;
+#endif
+      fseek(gcda_file, 0, SEEK_SET);
+
+      char* gcda_file_buffer = (char*)malloc(gcda_file_size + 1);
+      if (!gcda_file_buffer) {
+        cerr << __func__ << ":" << __LINE__
+             << "Unable to allocate memory to read a gcda file. " << endl;
+      } else {
+        if (fread(gcda_file_buffer, gcda_file_size, 1, gcda_file) != 1) {
+          cerr << __func__ << ":" << __LINE__
+               << "File read error" << endl;
+        } else {
+#if VTS_GCOV_DEBUG
+          cout << __func__ << ":" << __LINE__
+               << " GCDA field populated." << endl;
+#endif
+          gcda_file_buffer[gcda_file_size] = '\0';
+          NativeCodeCoverageRawDataMessage* raw_msg =
+              msg->mutable_raw_coverage_data()->Add();
+          raw_msg->set_file_path(filename.c_str());
+          string gcda_file_buffer_string(gcda_file_buffer, gcda_file_size);
+          raw_msg->set_gcda(gcda_file_buffer_string);
+          free(gcda_file_buffer);
+        }
+      }
+      fclose(gcda_file);
+    }
+#if USE_GCOV_DEBUG
+    if (result) {
+      for (unsigned int index = 0; index < result->size(); index++) {
+        cout << result->at(index) << endl;
+      }
+    }
+#endif
+    return true;
   }
-  DIR* srcdir = opendir(gcov_output_basepath_);
+  return false;
+}
+
+bool FuzzerBase::ScanAllGcdaFiles(
+    const string& basepath, FunctionSpecificationMessage* msg) {
+  DIR* srcdir = opendir(basepath.c_str());
   if (!srcdir) {
-    cerr << __func__ << " couln't open " << gcov_output_basepath_ << endl;
+    cerr << __func__ << ":" << __LINE__
+         << " couln't open " << basepath << endl;
     return false;
   }
 
   struct dirent* dent;
-  FILE* gcda_file;
   while ((dent = readdir(srcdir)) != NULL) {
+#if VTS_GCOV_DEBUG
+    cout << __func__ << ":" << __LINE__
+         << " readdir(" << basepath << ") for " << dent->d_name << endl;
+#endif
     struct stat st;
     if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) {
       continue;
@@ -490,64 +559,54 @@
       cerr << "error " << dent->d_name << endl;
       continue;
     }
-    if (!S_ISDIR(st.st_mode)) {
-      cout << dent->d_name << endl;
-      if (string(dent->d_name).rfind(".gcda") != string::npos) {
-        char* buffer;
-        buffer = (char*)malloc(strlen(gcov_output_basepath_) +
-                               strlen(dent->d_name) + 2);
-        if (!buffer) {
-          cerr << __FUNCTION__ << ": OOM" << endl;
-          closedir(srcdir);
-          return false;
-        }
-        sprintf(buffer, "%s/%s", gcov_output_basepath_, dent->d_name);
-
-        vector<unsigned>* processed_data = android::vts::parse_gcda_file(buffer);
-        for (const auto& data : *processed_data) {
-          msg->mutable_processed_coverage_data()->Add(data);
-        }
-
-        gcda_file = fopen(buffer, "rb");
-        if (!gcda_file) {
-          cerr << "Unable to open a gcda file. " << buffer << endl;
-        } else {
-          cout << "Opened a gcda file. " << buffer << endl;
-          fseek(gcda_file, 0, SEEK_END);
-          long gcda_file_size = ftell(gcda_file);
-          cout << "File size " << gcda_file_size << " bytes" << endl;
-          fseek(gcda_file, 0, SEEK_SET);
-
-          char* gcda_file_buffer = (char*)malloc(gcda_file_size + 1);
-          if (!gcda_file_buffer) {
-            cerr << "Unable to allocate memory to read a gcda file. " << endl;
-          } else {
-            if (fread(gcda_file_buffer, gcda_file_size, 1, gcda_file) != 1) {
-              cerr << "File read error" << endl;
-            } else {
-              gcda_file_buffer[gcda_file_size] = '\0';
-              NativeCodeCoverageRawDataMessage* raw_msg =
-                  msg->mutable_raw_coverage_data()->Add();
-              raw_msg->set_file_path(dent->d_name);
-              string gcda_file_buffer_string(gcda_file_buffer, gcda_file_size);
-              raw_msg->set_gcda(gcda_file_buffer_string);
-              free(gcda_file_buffer);
-            }
-          }
-          fclose(gcda_file);
-        }
-#if USE_GCOV_DEBUG
-        if (result) {
-          for (unsigned int index = 0; index < result->size(); index++) {
-            cout << result->at(index) << endl;
-          }
-        }
-#endif
-        free(buffer);
-        break;
-      }
+    if (S_ISDIR(st.st_mode)) {
+      ScanAllGcdaFiles(basepath + "/" + dent->d_name, msg);
+    } else {
+      ReadGcdaFile(basepath, dent->d_name, msg);
     }
   }
+#if VTS_GCOV_DEBUG
+  cout << __func__ << ":" << __LINE__
+       << " closedir(" << srcdir << ")" << endl;
+#endif
+  closedir(srcdir);
+  return true;
+}
+
+bool FuzzerBase::FunctionCallEnd(FunctionSpecificationMessage* msg) {
+  cout << __func__ << ": gcov flush " << endl;
+#if USE_GCOV
+  target_loader_.GcovFlush();
+  // find the file.
+  if (!gcov_output_basepath_) {
+    cerr << __FUNCTION__ << ": no gcov basepath set" << endl;
+    return ScanAllGcdaFiles(default_gcov_output_basepath, msg);
+  }
+  DIR* srcdir = opendir(gcov_output_basepath_);
+  if (!srcdir) {
+    cerr << __func__ << " couln't open " << gcov_output_basepath_ << endl;
+    return false;
+  }
+
+  struct dirent* dent;
+  while ((dent = readdir(srcdir)) != NULL) {
+    cout << __func__ << ": readdir(" << srcdir << ") for " << dent->d_name
+         << endl;
+
+    struct stat st;
+    if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) {
+      continue;
+    }
+    if (fstatat(dirfd(srcdir), dent->d_name, &st, 0) < 0) {
+      cerr << "error " << dent->d_name << endl;
+      continue;
+    }
+    if (!S_ISDIR(st.st_mode)
+        && ReadGcdaFile(gcov_output_basepath_, dent->d_name, msg)) {
+      break;
+    }
+  }
+  cout << __func__ << ": closedir(" << srcdir << ")" << endl;
   closedir(srcdir);
 #endif
   return true;
diff --git a/sysfuzzer/common/include/fuzz_tester/FuzzerBase.h b/sysfuzzer/common/include/fuzz_tester/FuzzerBase.h
index e370322..35307de 100644
--- a/sysfuzzer/common/include/fuzz_tester/FuzzerBase.h
+++ b/sysfuzzer/common/include/fuzz_tester/FuzzerBase.h
@@ -68,7 +68,15 @@
   // Called after calling a target function. Fills in the code coverage info.
   bool FunctionCallEnd(FunctionSpecificationMessage* msg);
 
+  // Scans all GCDA files under a given dir and adds to the message.
+  bool ScanAllGcdaFiles(
+      const string& basepath, FunctionSpecificationMessage* msg);
+
  protected:
+  bool ReadGcdaFile(
+      const string& basepath, const string& filename,
+      FunctionSpecificationMessage* msg);
+
   // a pointer to a HAL data structure of the loaded component.
   struct hw_device_t* device_;
 
diff --git a/testcases/hal/light/fuzz/ILightSetLight_fuzzer.cpp b/testcases/hal/light/fuzz/ILightSetLight_fuzzer.cpp
index 8c8b136..8557339 100644
--- a/testcases/hal/light/fuzz/ILightSetLight_fuzzer.cpp
+++ b/testcases/hal/light/fuzz/ILightSetLight_fuzzer.cpp
@@ -14,14 +14,11 @@
  * limitations under the License.
  */
 
-#include <algorithm>
-
 #include <FuzzerInterface.h>
 #include <android/hardware/light/2.0/ILight.h>
 
 using ::android::hardware::light::V2_0::ILight;
 using ::android::hardware::light::V2_0::LightState;
-using ::android::hardware::light::V2_0::Status;
 using ::android::hardware::light::V2_0::Type;
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
@@ -36,8 +33,8 @@
   memcpy(&type, data, sizeof(Type));
 
   LightState light_state;
-
   size_t copy_amount = std::min(sizeof(LightState), size - sizeof(Type));
+  data += sizeof(Type);
   memcpy(&light_state, data, copy_amount);
 
   light_hal->setLight(type, light_state);
diff --git a/testcases/hal/nfc/hidl/host/NfcHidlBasicTest.py b/testcases/hal/nfc/hidl/host/NfcHidlBasicTest.py
index cfffe50..5f11a15 100644
--- a/testcases/hal/nfc/hidl/host/NfcHidlBasicTest.py
+++ b/testcases/hal/nfc/hidl/host/NfcHidlBasicTest.py
@@ -100,6 +100,16 @@
         result = self.dut.hal.nfc.close()
         logging.info("close result: %s", result)
 
+        coverage = self.dut.hal.nfc.GetRawCodeCoverage()
+        if coverage:
+            last_coverage_data = {}
+            for coverage_msg in coverage:
+                logging.info("coverage file_path %s",
+                             coverage_msg.file_path)
+                logging.info("coverage gcda len %d bytes",
+                             len(coverage_msg.gcda))
+                last_coverage_data[coverage_msg.file_path] = coverage_msg.gcda
+            self.SetCoverageData(last_coverage_data)
 
 if __name__ == "__main__":
     test_runner.main()
diff --git a/testcases/hal/power/Android.mk b/testcases/hal/power/Android.mk
new file mode 100644
index 0000000..f9e3276
--- /dev/null
+++ b/testcases/hal/power/Android.mk
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(call all-subdir-makefiles)
diff --git a/testcases/hal/power/__init__.py b/testcases/hal/power/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/testcases/hal/power/__init__.py
diff --git a/testcases/hal/power/fuzz/Android.mk b/testcases/hal/power/fuzz/Android.mk
new file mode 100644
index 0000000..f7a31d9
--- /dev/null
+++ b/testcases/hal/power/fuzz/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+module_name := IPowerPowerHint_fuzzer
+module_src_files := IPowerPowerHint_fuzzer.cpp
+module_shared_libraries := android.hardware.power@1.0
+include test/vts/testcases/hal/common/fuzz/Android.hal_fuzzer.mk
+
+include $(CLEAR_VARS)
+module_name := IPowerSetFeature_fuzzer
+module_src_files := IPowerSetFeature_fuzzer.cpp
+module_shared_libraries := android.hardware.power@1.0
+include test/vts/testcases/hal/common/fuzz/Android.hal_fuzzer.mk
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := IPowerFuzzTest
+VTS_CONFIG_SRC_DIR := testcases/hal/power/fuzz
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/hal/power/fuzz/AndroidTest.xml b/testcases/hal/power/fuzz/AndroidTest.xml
new file mode 100644
index 0000000..b2edb32
--- /dev/null
+++ b/testcases/hal/power/fuzz/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VTS Power HIDL HAL's target-side fuzz test cases">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="LLVMFuzzerTest.push" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer" />
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="IPowerFuzzTest" />
+        <option name="binary-test-sources" value="
+            hal_fuzz/IPowerPowerHint_fuzzer,
+            hal_fuzz/IPowerSetFeature_fuzzer
+            "/>
+        <option name="binary-test-type" value="llvmfuzzer" />
+    </test>
+</configuration>
diff --git a/testcases/hal/power/fuzz/IPowerPowerHint_fuzzer.cpp b/testcases/hal/power/fuzz/IPowerPowerHint_fuzzer.cpp
new file mode 100644
index 0000000..5941225
--- /dev/null
+++ b/testcases/hal/power/fuzz/IPowerPowerHint_fuzzer.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <FuzzerInterface.h>
+#include <android/hardware/power/1.0/IPower.h>
+
+using ::android::hardware::power::V1_0::IPower;
+using ::android::hardware::power::V1_0::PowerHint;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+  static ::android::sp<IPower> power_hal = IPower::getService("power", true);
+  if (power_hal == nullptr) {
+    return 0;
+  }
+  if (size < sizeof(PowerHint)) {
+    return 0;
+  }
+  PowerHint hint;
+  memcpy(&hint, data, sizeof(PowerHint));
+
+  int32_t payload = 0;
+  size_t copy_amount = std::min(sizeof(int32_t), size - sizeof(PowerHint));
+  data += sizeof(PowerHint);
+  memcpy(&payload, data, copy_amount);
+
+  power_hal->powerHint(hint, payload);
+  return 0;
+}
diff --git a/testcases/hal/power/fuzz/IPowerSetFeature_fuzzer.cpp b/testcases/hal/power/fuzz/IPowerSetFeature_fuzzer.cpp
new file mode 100644
index 0000000..c9fe50f
--- /dev/null
+++ b/testcases/hal/power/fuzz/IPowerSetFeature_fuzzer.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <FuzzerInterface.h>
+#include <android/hardware/power/1.0/IPower.h>
+
+using ::android::hardware::power::V1_0::IPower;
+using ::android::hardware::power::V1_0::Feature;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+  static ::android::sp<IPower> power_hal = IPower::getService("power", true);
+  if (power_hal == nullptr) {
+    return 0;
+  }
+  if (size < sizeof(Feature)) {
+    return 0;
+  }
+  Feature feature;
+  memcpy(&feature, data, sizeof(Feature));
+
+  bool activate;
+  size_t copy_amount = std::min(sizeof(activate), size - sizeof(Feature));
+  data += sizeof(Feature);
+  memcpy(&activate, data, copy_amount);
+
+  power_hal->setFeature(feature, activate);
+  return 0;
+}
diff --git a/tools/vts-tradefed/res/config/vts-serving-staging-fuzz.xml b/tools/vts-tradefed/res/config/vts-serving-staging-fuzz.xml
index d46b289..c778a0f 100644
--- a/tools/vts-tradefed/res/config/vts-serving-staging-fuzz.xml
+++ b/tools/vts-tradefed/res/config/vts-serving-staging-fuzz.xml
@@ -18,5 +18,6 @@
     <option name="plan" value="vts" />
     <option name="compatibility:include-filter" value="ILightFuzzTest" />
     <option name="compatibility:include-filter" value="ISensorsFuzzTest" />
+    <option name="compatibility:include-filter" value="IPowerFuzzTest" />
     <template-include name="reporters" default="basic-reporters" />
 </configuration>
diff --git a/utils/python/mirror/mirror_object.py b/utils/python/mirror/mirror_object.py
index 2071334..64d56c8 100644
--- a/utils/python/mirror/mirror_object.py
+++ b/utils/python/mirror/mirror_object.py
@@ -46,6 +46,8 @@
                       mirror.
         _callback_server: the instance of a callback server.
         _parent_path: the name of a sub struct this object mirrors.
+        _last_raw_code_coverage_data: NativeCodeCoverageRawDataMessage,
+                                      last seen raw code coverage data.
     """
 
     def __init__(self, client, msg, callback_server, parent_path=None):
@@ -53,6 +55,7 @@
         self._if_spec_msg = msg
         self._callback_server = callback_server
         self._parent_path = parent_path
+        self._last_raw_code_coverage_data = None
 
     def GetFunctionPointerID(self, function_pointer):
         """Returns the function pointer ID for the given one."""
@@ -443,6 +446,11 @@
                         func_msg.submodule_name = submodule_name
             result = self._client.CallApi(text_format.MessageToString(func_msg))
             logging.debug(result)
+            if (isinstance(result, tuple) and len(result) == 2 and
+                isinstance(result[1], dict) and "coverage" in result[1]):
+                self._last_raw_code_coverage_data = copy.copy(
+                    result[1]["coverage"])
+                return result[0]
             return result
 
         def MessageGenerator(*args, **kwargs):
@@ -585,3 +593,7 @@
             logging.debug("const %s *\n%s", api_name, arg_msg)
             return ConstGenerator()
         raise MirrorObjectError("unknown api name %s" % api_name)
+
+    def GetRawCodeCoverage(self):
+      """Returns any measured raw code coverage data."""
+      return self._last_raw_code_coverage_data
diff --git a/web/dashboard/appengine/servlet/pom.xml b/web/dashboard/appengine/servlet/pom.xml
index 8240052..de3ed0e 100644
--- a/web/dashboard/appengine/servlet/pom.xml
+++ b/web/dashboard/appengine/servlet/pom.xml
@@ -26,15 +26,16 @@
   <properties>
     <bigtable.version>0.9.1</bigtable.version>
     <hbase.version>1.2.1</hbase.version>
-    <bigtable.projectID>google.com:android-vts-internal</bigtable.projectID>
-    <bigtable.instanceID>vts-dev</bigtable.instanceID>
+    <bigtable.projectID></bigtable.projectID>
+    <bigtable.instanceID></bigtable.instanceID>
+    <appengine.clientID></appengine.clientID>
+    <appengine.serviceClientID></appengine.serviceClientID>
     <appengine.senderEmail></appengine.senderEmail>
     <appengine.emailDomain></appengine.emailDomain>
     <appengine.defaultEmail></appengine.defaultEmail>
-    <appengine.serviceClientID></appengine.serviceClientID>
-    <appengine.clientID></appengine.clientID>
     <gerrit.uri></gerrit.uri>
     <gerrit.scope></gerrit.scope>
+    <analytics.id></analytics.id>
 
     <hadoop.version>2.4.1</hadoop.version>
     <compat.module>hbase-hadoop2-compat</compat.module>
@@ -42,9 +43,6 @@
     <appengine.version>1.9.40</appengine.version>
     <gcloud.plugin.version>2.0.9.111.v20160527</gcloud.plugin.version>
 
-    <slf4j.version>1.7.12</slf4j.version>
-    <log4j.version>1.2.17</log4j.version>
-
     <requiredMavenVersion>3.3.3</requiredMavenVersion>
 
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -161,24 +159,12 @@
     </dependency>
 
     <dependency>
-      <groupId>com.google.api-client</groupId>
-      <artifactId>google-api-client</artifactId>
-      <version>1.20.0</version>
-    </dependency>
-
-    <dependency>
       <groupId>com.google.http-client</groupId>
       <artifactId>google-http-client</artifactId>
       <version>1.20.0</version>
     </dependency>
 
     <dependency>
-      <groupId>com.google.code.gson</groupId>
-      <artifactId>gson</artifactId>
-      <version>2.3.1</version>
-    </dependency>
-
-    <dependency>
       <groupId>com.google.http-client</groupId>
       <artifactId>google-http-client-jackson</artifactId>
       <version>1.20.0</version>
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/BaseServlet.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/BaseServlet.java
new file mode 100644
index 0000000..45e8847
--- /dev/null
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/BaseServlet.java
@@ -0,0 +1,56 @@
+package com.android.vts.servlet;
+
+import com.google.appengine.api.users.User;
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
+import com.google.gson.Gson;
+import java.io.IOException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.logging.Logger;
+
+public abstract class BaseServlet extends HttpServlet {
+    protected final Logger logger = Logger.getLogger(getClass().getName());
+
+    // Environment variables
+    protected static final String EMAIL_DOMAIN = System.getenv("EMAIL_DOMAIN");
+    protected static final String SENDER_EMAIL = System.getenv("SENDER_EMAIL");
+    protected static final String DEFAULT_EMAIL = System.getenv("DEFAULT_EMAIL");
+    protected static final String GERRIT_URI = System.getenv("GERRIT_URI");
+    protected static final String GERRIT_SCOPE = System.getenv("GERRIT_SCOPE");
+    protected static final String CLIENT_ID = System.getenv("CLIENT_ID");
+    protected static final String ANALYTICS_ID = System.getenv("ANALYTICS_ID");
+
+    // Common constants
+    protected static final long ONE_DAY = 86400000000L;  // units microseconds
+    protected static final String TABLE_PREFIX = "result_";
+
+    @Override
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+        // If the user is logged out, allow them to log back in and return to the page.
+        // Set the logout URL to direct back to a login page that directs to the current request.
+        UserService userService = UserServiceFactory.getUserService();
+        User currentUser = userService.getCurrentUser();
+        String loginURI = userService.createLoginURL(request.getRequestURI());
+        String logoutURI = userService.createLogoutURL(loginURI);
+        if (currentUser == null || currentUser.getEmail() == null) {
+            response.sendRedirect(loginURI);
+            return;
+        }
+        request.setAttribute("logoutURL", logoutURI);
+        request.setAttribute("email", currentUser.getEmail());
+        request.setAttribute("analytics_id", new Gson().toJson(ANALYTICS_ID));
+        response.setContentType("text/html");
+        doGetHandler(request, response);
+    }
+
+    /**
+     * Implementation of the doGet method to be executed by servlet subclasses.
+     * @param request The HttpServletRequest object.
+     * @param response The HttpServletResponse object.
+     * @throws IOException
+     */
+    public abstract void doGetHandler(HttpServletRequest request, HttpServletResponse response)
+            throws IOException;
+}
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/DashboardMainServlet.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/DashboardMainServlet.java
index 5f914c8..b357cec 100644
--- a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/DashboardMainServlet.java
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/DashboardMainServlet.java
@@ -28,8 +28,6 @@
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.util.Bytes;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -37,25 +35,22 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.logging.Level;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 /**
  * Represents the servlet that is invoked on loading the first page of dashboard.
  */
-@WebServlet(name = "dashboard_main", urlPatterns = {"/"})
-public class DashboardMainServlet extends HttpServlet {
+public class DashboardMainServlet extends BaseServlet {
 
     private static final String DASHBOARD_MAIN_JSP = "/dashboard_main.jsp";
     private static final String DASHBOARD_ALL_LINK = "/?showAll=true";
     private static final String DASHBOARD_FAVORITES_LINK = "/";
     private static final byte[] EMAIL_FAMILY = Bytes.toBytes("email_to_test");
     private static final String STATUS_TABLE = "vts_status_table";
-    private static final String TABLE_PREFIX = "result_";
     private static final String ALL_HEADER = "All Tests";
     private static final String FAVORITES_HEADER = "Favorites";
     private static final String NO_FAVORITES_ERROR = "No subscribed tests. Click the edit button to add to favorites.";
@@ -65,20 +60,13 @@
     private static final String UP_ARROW = "keyboard_arrow_up";
     private static final String DOWN_ARROW = "keyboard_arrow_down";
 
-    private static final Logger logger = LoggerFactory.getLogger(DashboardMainServlet.class);
-
 
     @Override
-    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    public void doGetHandler(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
         UserService userService = UserServiceFactory.getUserService();
         User currentUser = userService.getCurrentUser();
         RequestDispatcher dispatcher = null;
-        String loginURI = userService.createLoginURL(request.getRequestURI());
-        String logoutURI = userService.createLogoutURL(loginURI);
-        if (currentUser == null || currentUser.getEmail() == null) {
-            response.sendRedirect(loginURI);
-            return;
-        }
 
         Table table = BigtableHelper.getTable(TableName.valueOf(STATUS_TABLE));
         List<String> displayedTests = new ArrayList<>();
@@ -141,7 +129,6 @@
 
         String[] testArray = new String[displayedTests.size()];
         displayedTests.toArray(testArray);
-        response.setContentType("text/plain");
         response.setStatus(HttpServletResponse.SC_OK);
         request.setAttribute("testNames", testArray);
         request.setAttribute("headerLabel", header);
@@ -149,14 +136,12 @@
         request.setAttribute("buttonLabel", buttonLabel);
         request.setAttribute("buttonIcon", buttonIcon);
         request.setAttribute("buttonLink", buttonLink);
-        request.setAttribute("logoutURL", logoutURI);
-        request.setAttribute("email", currentUser.getEmail());
         request.setAttribute("error", error);
         dispatcher = request.getRequestDispatcher(DASHBOARD_MAIN_JSP);
         try {
             dispatcher.forward(request, response);
         } catch (ServletException e) {
-            logger.info("Servlet Excpetion caught : ", e);
+            logger.log(Level.SEVERE, "Servlet Excpetion caught : ", e);
         }
     }
 }
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowCoverageServlet.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowCoverageServlet.java
index bce5613..08cb65d 100644
--- a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowCoverageServlet.java
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowCoverageServlet.java
@@ -21,9 +21,6 @@
 import com.android.vts.proto.VtsReportMessage.CoverageReportMessage;
 import com.android.vts.proto.VtsReportMessage.TestCaseReportMessage;
 import com.android.vts.proto.VtsReportMessage.TestReportMessage;
-import com.google.appengine.api.users.User;
-import com.google.appengine.api.users.UserService;
-import com.google.appengine.api.users.UserServiceFactory;
 import com.google.gson.Gson;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.Result;
@@ -31,15 +28,12 @@
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.util.Bytes;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.logging.Level;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -47,23 +41,14 @@
 /**
  * Servlet for handling requests to show code coverage.
  */
-@WebServlet(name = "show_coverage", urlPatterns = {"/show_coverage"})
-public class ShowCoverageServlet extends HttpServlet {
+public class ShowCoverageServlet extends BaseServlet {
 
     private static final byte[] FAMILY = Bytes.toBytes("test");
     private static final byte[] QUALIFIER = Bytes.toBytes("data");
-    private static final String TABLE_PREFIX = "result_";
-    private static final String GERRIT_URI = System.getenv("GERRIT_URI");
-    private static final String GERRIT_SCOPE = System.getenv("GERRIT_SCOPE");
-    private static final String CLIENT_ID = System.getenv("CLIENT_ID");
-    private static final Logger logger = LoggerFactory.getLogger(ShowCoverageServlet.class);
 
     @Override
-    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        UserService userService = UserServiceFactory.getUserService();
-        User currentUser = userService.getCurrentUser();
-        String loginURI = userService.createLoginURL(request.getRequestURI());
-        String logoutURI = userService.createLogoutURL(loginURI);
+    public void doGetHandler(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
         RequestDispatcher dispatcher = null;
         Table table = null;
         TableName tableName = null;
@@ -125,7 +110,6 @@
             }
         }
 
-        request.setAttribute("email", currentUser.getEmail());
         request.setAttribute("testName", request.getParameter("testName"));
         request.setAttribute("gerritURI", new Gson().toJson(GERRIT_URI));
         request.setAttribute("gerritScope", new Gson().toJson(GERRIT_SCOPE));
@@ -137,13 +121,12 @@
         request.setAttribute("commits", new Gson().toJson(commits));
         request.setAttribute("startTime", request.getParameter("startTime"));
         request.setAttribute("endTime", request.getParameter("endTime"));
-        response.setContentType("text/plain");
         dispatcher = request.getRequestDispatcher("/show_coverage.jsp");
 
         try {
             dispatcher.forward(request, response);
         } catch (ServletException e) {
-            logger.error("Servlet Excpetion caught : ", e);
+            logger.log(Level.SEVERE, "Servlet Exception caught : ", e);
         }
     }
 }
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowGraphServlet.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowGraphServlet.java
index 2f17d17..bf08447 100644
--- a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowGraphServlet.java
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowGraphServlet.java
@@ -20,9 +20,6 @@
 import com.android.vts.proto.VtsReportMessage;
 import com.android.vts.proto.VtsReportMessage.ProfilingReportMessage;
 import com.android.vts.proto.VtsReportMessage.TestReportMessage;
-import com.google.appengine.api.users.User;
-import com.google.appengine.api.users.UserService;
-import com.google.appengine.api.users.UserServiceFactory;
 import org.apache.commons.math3.stat.descriptive.rank.Percentile;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.Result;
@@ -31,18 +28,15 @@
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.util.Bytes;
 import com.google.gson.Gson;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.logging.Level;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -50,25 +44,18 @@
 /**
  * Servlet for handling requests to load graphs.
  */
-@WebServlet(name = "show_graph", urlPatterns = {"/show_graph"})
-public class ShowGraphServlet extends HttpServlet {
+public class ShowGraphServlet extends BaseServlet {
 
     private static final String PROFILING_DATA_ALERT = "No profiling data was found.";
     private static final byte[] FAMILY = Bytes.toBytes("test");
     private static final byte[] QUALIFIER = Bytes.toBytes("data");
-    private static final String TABLE_PREFIX = "result_";
-    private static final long ONE_DAY = 86400000000000L;  // units microseconds
-    private static final Logger logger = LoggerFactory.getLogger(ShowGraphServlet.class);
 
     @Override
-    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    public void doGetHandler(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
         RequestDispatcher dispatcher = null;
         Table table = null;
         TableName tableName = null;
-        UserService userService = UserServiceFactory.getUserService();
-        User currentUser = userService.getCurrentUser();
-        String loginURI = userService.createLoginURL(request.getRequestURI());
-        String logoutURI = userService.createLogoutURL(loginURI);
 
         String profilingPointName = request.getParameter("profilingPoint");
         long startTime;
@@ -197,8 +184,6 @@
             request.setAttribute("error", PROFILING_DATA_ALERT);
         }
 
-        request.setAttribute("logoutURL", logoutURI);
-        request.setAttribute("email", currentUser.getEmail());
         // performance data for scatter plot
         request.setAttribute("showProfilingGraph", showProfilingGraph);
         request.setAttribute("showPerformanceGraph", showPerformanceGraph);
@@ -214,12 +199,11 @@
 
         request.setAttribute("profilingPointName", profilingPointName);
         request.setAttribute("percentileValuesJson", new Gson().toJson(percentileValuesArray));
-        response.setContentType("text/plain");
         dispatcher = request.getRequestDispatcher("/show_graph.jsp");
         try {
             dispatcher.forward(request, response);
         } catch (ServletException e) {
-            logger.error("Servlet Excpetion caught : ", e);
+            logger.log(Level.SEVERE, "Servlet Excpetion caught : ", e);
         }
     }
 }
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowPreferencesServlet.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowPreferencesServlet.java
index 5795a60..b960fc3 100644
--- a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowPreferencesServlet.java
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowPreferencesServlet.java
@@ -31,48 +31,38 @@
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.util.Bytes;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.logging.Level;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 /**
  * Represents the servlet that is invoked on loading the preferences page to manage favorites.
  */
-@WebServlet(name = "preferences", urlPatterns = {"/show_preferences"})
-public class ShowPreferencesServlet extends HttpServlet {
+public class ShowPreferencesServlet extends BaseServlet {
 
     private static final String PREFERENCES_JSP = "/show_preferences.jsp";
     private static final String DASHBOARD_MAIN_LINK = "/";
     private static final byte[] EMAIL_FAMILY = Bytes.toBytes("email_to_test");
     private static final byte[] TEST_FAMILY = Bytes.toBytes("test_to_email");
     private static final String STATUS_TABLE = "vts_status_table";
-    private static final String TABLE_PREFIX = "result_";
-    private static final Logger logger = LoggerFactory.getLogger(ShowPreferencesServlet.class);
 
 
     @Override
-    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    public void doGetHandler(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
         // Get the user's information
         UserService userService = UserServiceFactory.getUserService();
         User currentUser = userService.getCurrentUser();
         RequestDispatcher dispatcher = null;
 
-        // If the user is logged out, allow them to log back in and return to the page.
-        // Set the logout URL to direct back to a login page that directs to the current request.
-        String loginURI = userService.createLoginURL(request.getRequestURI());
-        String logoutURI = userService.createLogoutURL(loginURI);
-
         Table table = BigtableHelper.getTable(TableName.valueOf(STATUS_TABLE));
         List<String> subscribedTests = new ArrayList<>();
 
@@ -109,17 +99,14 @@
             }
         }
 
-        response.setContentType("text/plain");
         request.setAttribute("allTestsJson", new Gson().toJson(allTests));
         request.setAttribute("subscribedTests", subscribedTests);
         request.setAttribute("subscribedTestsJson", new Gson().toJson(subscribedTests));
-        request.setAttribute("logoutURL", logoutURI);
-        request.setAttribute("email", currentUser.getEmail());
         dispatcher = request.getRequestDispatcher(PREFERENCES_JSP);
         try {
             dispatcher.forward(request, response);
         } catch (ServletException e) {
-            logger.info("Servlet Excpetion caught : ", e);
+            logger.log(Level.SEVERE, "Servlet Excpetion caught : ", e);
         }
     }
 
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowTableServlet.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowTableServlet.java
index 855e938..bad84e1 100644
--- a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowTableServlet.java
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/ShowTableServlet.java
@@ -25,9 +25,6 @@
 import com.android.vts.proto.VtsReportMessage.TestCaseResult;
 import com.android.vts.proto.VtsReportMessage.TestReportMessage;
 
-import com.google.appengine.api.users.User;
-import com.google.appengine.api.users.UserService;
-import com.google.appengine.api.users.UserServiceFactory;
 import com.google.gson.Gson;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hadoop.hbase.TableName;
@@ -36,8 +33,6 @@
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.util.Bytes;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -49,13 +44,11 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.logging.Level;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import javax.mail.MessagingException;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -63,10 +56,8 @@
 /**
  * Servlet for handling requests to load individual tables.
  */
-@WebServlet(name = "show_table", urlPatterns = {"/show_table"})
-public class ShowTableServlet extends HttpServlet {
+public class ShowTableServlet extends BaseServlet {
 
-    private static final Logger logger = LoggerFactory.getLogger(ShowTableServlet.class);
     // Error message displayed on the webpage is tableName passed is null.
     private static final String TABLE_NAME_ERROR = "Error : Table name must be passed!";
     private static final String PROFILING_DATA_ALERT = "No profiling data was found.";
@@ -74,7 +65,6 @@
     private static final int TIME_INFO_ROW_COUNT = 2;
     private static final int DURATION_INFO_ROW_COUNT = 1;
     private static final int SUMMARY_ROW_COUNT = 5;
-    private static final long ONE_DAY = 86400000000L;  // units microseconds
     private static final String[] SEARCH_KEYS = {"devicebuildid", "branch", "target", "device",
                                                  "vtsbuildid"};
     private static final String SEARCH_HELP_HEADER = "Search Help";
@@ -85,11 +75,9 @@
         "a field-specific filter if specified in the format: \"field=value\".<br><br>" +
         "<b>Supported field qualifiers:</b> " + StringUtils.join(SEARCH_KEYS, ", ") + ".";
     private static final Set<String> SEARCH_KEYSET = new HashSet<String>(Arrays.asList(SEARCH_KEYS));
-    private static final String TABLE_PREFIX = "result_";
     private static final byte[] FAMILY = Bytes.toBytes("test");
     private static final byte[] QUALIFIER = Bytes.toBytes("data");
 
-
     /**
      * Parse the search string to populate the searchPairs map and the generalTerms set.
      * General terms apply to any field, while pairs in searchPairs are for a particular field.
@@ -176,11 +164,8 @@
     }
 
     @Override
-    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        UserService userService = UserServiceFactory.getUserService();
-        User currentUser = userService.getCurrentUser();
-        String loginURI = userService.createLoginURL(request.getRequestURI());
-        String logoutURI = userService.createLogoutURL(loginURI);
+    public void doGetHandler(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
         boolean unfiltered = request.getParameter("unfiltered") != null;
         boolean showPresubmit = request.getParameter("showPresubmit") != null;
         boolean showPostsubmit = request.getParameter("showPostsubmit") != null;
@@ -261,8 +246,6 @@
                 return new Long(report2.getStartTimestamp()).compareTo(report1.getStartTimestamp());
             }
         };
-
-        response.setContentType("text/plain");
         table = BigtableHelper.getTable(tableName);
 
         // this is the tip of the tree and is used for populating pie chart.
@@ -539,8 +522,6 @@
         boolean hasNewer = BigtableHelper.hasNewer(table, endTime);
         boolean hasOlder = BigtableHelper.hasOlder(table, startTime);
 
-        request.setAttribute("logoutURL", logoutURI);
-        request.setAttribute("email", currentUser.getEmail());
         request.setAttribute("testName", request.getParameter("testName"));
 
         request.setAttribute("error", profilingDataAlert);
@@ -574,7 +555,7 @@
         try {
             dispatcher.forward(request, response);
         } catch (ServletException e) {
-            logger.error("Servlet Exception caught : " + e.toString());
+            logger.log(Level.SEVERE, "Servlet Exception caught : " + e.toString());
         }
     }
 }
\ No newline at end of file
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/VtsAlertJobServlet.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/VtsAlertJobServlet.java
index 3345b19..66318dc 100644
--- a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/VtsAlertJobServlet.java
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/servlet/VtsAlertJobServlet.java
@@ -26,8 +26,6 @@
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.util.Bytes;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import com.android.vts.helpers.BigtableHelper;
 import com.android.vts.proto.VtsReportMessage;
 import com.android.vts.proto.VtsReportMessage.AndroidDeviceInfoMessage;
@@ -51,13 +49,13 @@
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.logging.Level;
 import javax.mail.Message;
 import javax.mail.MessagingException;
 import javax.mail.Session;
 import javax.mail.Transport;
 import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeMessage;
-import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -65,8 +63,7 @@
 /**
  * Represents the notifications service which is automatically called on a fixed schedule.
  */
-@WebServlet(name = "vts_alert_job", urlPatterns = {"/cron/vts_alert_job"})
-public class VtsAlertJobServlet extends HttpServlet {
+public class VtsAlertJobServlet extends BaseServlet {
 
     private static final byte[] RESULTS_FAMILY = Bytes.toBytes("test");
     private static final byte[] TEST_FAMILY = Bytes.toBytes("test_to_email");
@@ -75,16 +72,9 @@
     private static final byte[] TIME_QUALIFIER = Bytes.toBytes("upload_timestamp");
     private static final String STATUS_TABLE = "vts_status_table";
     private static final String VTS_EMAIL_NAME = "VTS Alert Bot";
-    private static final String TABLE_PREFIX = "result_";
-    private static final String EMAIL_DOMAIN = System.getenv("EMAIL_DOMAIN");
-    private static final String SENDER_EMAIL = System.getenv("SENDER_EMAIL");
-    private static final String DEFAULT_EMAIL = System.getenv("DEFAULT_EMAIL");
     private static final long MILLI_TO_MICRO = 1000;  // conversion factor from milli to micro units
-    private static final long ONE_DAY = 86400000000L;  // units microseconds
     private static final long THREE_MINUTES = 180000000L;  // units microseconds
 
-    private static final Logger logger = LoggerFactory.getLogger(DashboardMainServlet.class);
-
     /**
      * Fetches the list of subscriber email addresses for a test.
      * @param statusTable The Table instance for the VTS status table.
@@ -139,7 +129,7 @@
                                  new InternetAddress(email, email));
             } catch (MessagingException | UnsupportedEncodingException e) {
                 // Gracefully continue when a subscriber email is invalid.
-                logger.warn("Error sending email to recipient " + email + " : ", e);
+                logger.log(Level.WARNING, "Error sending email to recipient " + email + " : ", e);
             }
         }
         msg.setFrom(new InternetAddress(SENDER_EMAIL, VTS_EMAIL_NAME));
@@ -191,7 +181,7 @@
                     try {
                         messages.add(composeEmail(emails, subject, body));
                     } catch (MessagingException | UnsupportedEncodingException e) {
-                        logger.error("Error composing email : ", e);
+                        logger.log(Level.WARNING, "Error composing email : ", e);
                     }
                 }
                 return testStatusMessage.toByteArray();
@@ -352,7 +342,7 @@
             try {
                 messages.add(composeEmail(emails, subject, body));
             } catch (MessagingException | UnsupportedEncodingException e) {
-                logger.error("Error composing email : ", e);
+                logger.log(Level.WARNING, "Error composing email : ", e);
             }
         } else if (continuedTestcaseFailures.size() > 0) {
             String subject = "Continued test failures in " + test + " @ " + buildId;
@@ -362,7 +352,7 @@
             try {
                 messages.add(composeEmail(emails, subject, body));
             } catch (MessagingException | UnsupportedEncodingException e) {
-                logger.error("Error composing email : ", e);
+                logger.log(Level.WARNING, "Error composing email : ", e);
             }
         } else if (transientTestcaseFailures.size() > 0) {
             String subject = "Transient test failure in " + test + " @ " + buildId;
@@ -372,7 +362,7 @@
             try {
                 messages.add(composeEmail(emails, subject, body));
             } catch (MessagingException | UnsupportedEncodingException e) {
-                logger.error("Error composing email : ", e);
+                logger.log(Level.WARNING, "Error composing email : ", e);
             }
         } else if (fixedTestcases.size() > 0) {
             String subject = "All test cases passing in " + test + " @ " + buildId;
@@ -382,7 +372,7 @@
             try {
                 messages.add(composeEmail(emails, subject, body));
             } catch (MessagingException | UnsupportedEncodingException e) {
-                logger.error("Error composing email : ", e);
+                logger.log(Level.WARNING, "Error composing email : ", e);
             }
         }
         Builder builder = VtsWebStatusMessage.TestStatusMessage.newBuilder();
@@ -393,7 +383,14 @@
     }
 
     @Override
-    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    public void doGet(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
+        doGetHandler(request, response);
+    }
+
+    @Override
+    public void doGetHandler(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
         Table table = BigtableHelper.getTable(TableName.valueOf(STATUS_TABLE));
         Scan scan = new Scan();
         scan.addFamily(STATUS_FAMILY);
@@ -410,7 +407,7 @@
                     result.getValue(STATUS_FAMILY, TIME_QUALIFIER)));
             } catch (NumberFormatException e) {
                 // If no upload timestamp, skip this row.
-                logger.warn("Error parsing upload timestamp: ", e);
+                logger.log(Level.WARNING, "Error parsing upload timestamp: ", e);
                 continue;
             }
             List<Message> messageQueue = new ArrayList<>();
@@ -435,7 +432,7 @@
                     try {
                         Transport.send(msg);
                     } catch (MessagingException e) {
-                        logger.error("Error sending email : ", e);
+                        logger.log(Level.WARNING, "Error sending email : ", e);
                     }
                 }
             }
diff --git a/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/appengine-web.xml b/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/appengine-web.xml
index f6e468a..7ffd353 100644
--- a/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/appengine-web.xml
+++ b/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/appengine-web.xml
@@ -36,11 +36,11 @@
       <env-var name="CLIENT_ID" value="${appengine.clientID}" />
       <env-var name="GERRIT_URI" value="${gerrit.uri}" />
       <env-var name="GERRIT_SCOPE" value="${gerrit.scope}" />
+      <env-var name="ANALYTICS_ID" value="${analytics.id}" />
     </env-variables>
 
     <system-properties>
       <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
-      <property name="log4j.debug" value="true" />
     </system-properties>
 
     <beta-settings>
diff --git a/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/logging.properties b/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/logging.properties
index a3aa3e1..f3bc3b7 100644
--- a/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/logging.properties
+++ b/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/logging.properties
@@ -25,18 +25,3 @@
 
 # Set the default logging level for all loggers to INFO
 .level = INFO
-
-# Loggers
-com.google.android.vts.servlet.BigtableHelper.level=ALL
-com.google.android.vts.servlet.helloworld.level=ALL
-com.google.cloud.bigtable.hbase1_1.level=ALL
-com.google.cloud.bigtable.level=ALL
-org.apache.hadoop.hbase.level=ALL
-
-org.apache.http.level=OFF
-org.apache.http.wire.level=OFF
-com.google.apphosting.repackaged.org.apache.http.wire.level=OFF
-
-com.google.apphosting.vmruntime.VmAppLogsWriter.level=OFF
-com.google.apphosting.vmruntime.VmApiProxyDelegate=OFF
-com.google.apphosting.vmruntime.level=OFF
diff --git a/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/web.xml b/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/web.xml
index 53037d7..5f5997e 100644
--- a/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/web.xml
+++ b/web/dashboard/appengine/servlet/src/main/webapp/WEB-INF/web.xml
@@ -19,32 +19,37 @@
 
 <servlet>
   <servlet-name>dashboard_main</servlet-name>
-  <servlet-class>com.google.android.vts.servlet.DashboardMainServlet</servlet-class>
+  <servlet-class>com.android.vts.servlet.DashboardMainServlet</servlet-class>
 </servlet>
 
 <servlet>
   <servlet-name>show_table</servlet-name>
-  <servlet-class>com.google.android.vts.servlet.ShowTableServlet</servlet-class>
+  <servlet-class>com.android.vts.servlet.ShowTableServlet</servlet-class>
 </servlet>
 
 <servlet>
   <servlet-name>show_graph</servlet-name>
-  <servlet-class>com.google.android.vts.servlet.ShowGraphServlet</servlet-class>
+  <servlet-class>com.android.vts.servlet.ShowGraphServlet</servlet-class>
 </servlet>
 
 <servlet>
   <servlet-name>show_coverage</servlet-name>
-  <servlet-class>com.google.android.vts.servlet.ShowCoverageServlet</servlet-class>
+  <servlet-class>com.android.vts.servlet.ShowCoverageServlet</servlet-class>
+</servlet>
+
+<servlet>
+  <servlet-name>show_preferences</servlet-name>
+  <servlet-class>com.android.vts.servlet.ShowPreferencesServlet</servlet-class>
 </servlet>
 
 <servlet>
   <servlet-name>bigtable</servlet-name>
-  <servlet-class>com.google.android.vts.api.BigtableApiServlet</servlet-class>
+  <servlet-class>com.android.vts.api.BigtableApiServlet</servlet-class>
 </servlet>
 
 <servlet>
   <servlet-name>vts_alert_job</servlet-name>
-  <servlet-class>com.google.android.vts.servlet.VtsAlertJobServlet</servlet-class>
+  <servlet-class>com.android.vts.servlet.VtsAlertJobServlet</servlet-class>
 </servlet>
 
 <servlet-mapping>
@@ -53,7 +58,7 @@
 </servlet-mapping>
 
 <servlet-mapping>
-  <servlet-name>preferences</servlet-name>
+  <servlet-name>show_preferences</servlet-name>
   <url-pattern>/show_preferences/*</url-pattern>
 </servlet-mapping>
 
@@ -83,6 +88,11 @@
 </servlet-mapping>
 
 <servlet-mapping>
+  <servlet-name>default</servlet-name>
+  <url-pattern>*.js</url-pattern>
+</servlet-mapping>
+
+<servlet-mapping>
   <servlet-name>vts_alert_job</servlet-name>
   <url-pattern>/cron/vts_alert_job/*</url-pattern>
 </servlet-mapping>
diff --git a/web/dashboard/appengine/servlet/src/main/webapp/dashboard_main.jsp b/web/dashboard/appengine/servlet/src/main/webapp/dashboard_main.jsp
index 44d3119..5b799d6 100644
--- a/web/dashboard/appengine/servlet/src/main/webapp/dashboard_main.jsp
+++ b/web/dashboard/appengine/servlet/src/main/webapp/dashboard_main.jsp
@@ -26,8 +26,12 @@
   <link rel='stylesheet' href='https://www.gstatic.com/external_hosted/materialize/all_styles-bundle.css'>
   <link rel='stylesheet' href='/css/navbar.css'>
   <link rel='stylesheet' href='/css/dashboard_main.css'>
+  <script src='/js/analytics.js' type='text/javascript'></script>
   <script src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>
   <script src='https://www.gstatic.com/external_hosted/materialize/materialize.min.js'></script>
+  <script>
+      if (${analytics_id}) analytics_init(${analytics_id});
+  </script>
   <head>
     <title>VTS Dashboard</title>
 
diff --git a/web/dashboard/appengine/servlet/src/main/webapp/js/analytics.js b/web/dashboard/appengine/servlet/src/main/webapp/js/analytics.js
new file mode 100644
index 0000000..94a5350
--- /dev/null
+++ b/web/dashboard/appengine/servlet/src/main/webapp/js/analytics.js
@@ -0,0 +1,10 @@
+function analytics_init (id) {
+  // Autogenerated from Google Analytics
+  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+
+    ga('create', id, 'auto');
+    ga('send', 'pageview');
+}
\ No newline at end of file
diff --git a/web/dashboard/appengine/servlet/src/main/webapp/show_coverage.jsp b/web/dashboard/appengine/servlet/src/main/webapp/show_coverage.jsp
index df49c8a..21603cd 100644
--- a/web/dashboard/appengine/servlet/src/main/webapp/show_coverage.jsp
+++ b/web/dashboard/appengine/servlet/src/main/webapp/show_coverage.jsp
@@ -27,10 +27,12 @@
     <link rel="stylesheet" href="https://www.gstatic.com/external_hosted/materialize/all_styles-bundle.css">
     <link rel="stylesheet" href="/css/navbar.css">
     <link rel="stylesheet" href="/css/show_coverage.css">
+    <script src='/js/analytics.js' type='text/javascript'></script>
     <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
     <script src="https://www.gstatic.com/external_hosted/materialize/materialize.min.js"></script>
     <script src="https://apis.google.com/js/api.js" type="text/javascript"></script>
     <script type="text/javascript">
+        if (${analytics_id}) analytics_init(${analytics_id});
         $(document).ready(function() {
             // Initialize AJAX for CORS
             $.ajaxSetup({
diff --git a/web/dashboard/appengine/servlet/src/main/webapp/show_graph.jsp b/web/dashboard/appengine/servlet/src/main/webapp/show_graph.jsp
index 9438c63..32f2a77 100644
--- a/web/dashboard/appengine/servlet/src/main/webapp/show_graph.jsp
+++ b/web/dashboard/appengine/servlet/src/main/webapp/show_graph.jsp
@@ -25,12 +25,14 @@
     <link type="text/css" href="/css/navbar.css" rel="stylesheet">
     <link type="text/css" href="/css/show_graph.css" rel="stylesheet">
     <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/jquery-ui.css">
+    <script src="/js/analytics.js" type="text/javascript"></script>
     <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
     <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
     <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
-    <script src="https://www.gstatic.com/external_hosted/materialize/materialize.min.js"></script>
+    <script src='/js/analytics.js' type='text/javascript'></script>
     <title>Graph</title>
     <script type="text/javascript">
+      if (${analytics_id}) analytics_init(${analytics_id});
       google.charts.load("current", {packages:["corechart", "table", "line"]});
       google.charts.setOnLoadCallback(drawProfilingChart);
       google.charts.setOnLoadCallback(drawPerformanceChart);
diff --git a/web/dashboard/appengine/servlet/src/main/webapp/show_preferences.jsp b/web/dashboard/appengine/servlet/src/main/webapp/show_preferences.jsp
index 78af965..ced594e 100644
--- a/web/dashboard/appengine/servlet/src/main/webapp/show_preferences.jsp
+++ b/web/dashboard/appengine/servlet/src/main/webapp/show_preferences.jsp
@@ -26,6 +26,7 @@
   <link rel='stylesheet' href='https://www.gstatic.com/external_hosted/materialize/all_styles-bundle.css'>
   <link rel='stylesheet' href='/css/navbar.css'>
   <link rel='stylesheet' href='/css/show_preferences.css'>
+  <script src='/js/analytics.js' type='text/javascript'></script>
   <script src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>
   <script src='https://www.gstatic.com/external_hosted/materialize/materialize.min.js'></script>
   <script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js'></script>
@@ -50,6 +51,7 @@
       </div>
     </nav>
     <script>
+        if (${analytics_id}) analytics_init(${analytics_id});
         var subscribedSet = new Set(${subscribedTestsJson});
         var displayedSet = new Set(${subscribedTestsJson});
         var allTests = ${allTestsJson};
diff --git a/web/dashboard/appengine/servlet/src/main/webapp/show_table.jsp b/web/dashboard/appengine/servlet/src/main/webapp/show_table.jsp
index cd6b2db..f7ea774 100644
--- a/web/dashboard/appengine/servlet/src/main/webapp/show_table.jsp
+++ b/web/dashboard/appengine/servlet/src/main/webapp/show_table.jsp
@@ -26,11 +26,13 @@
     <link rel='stylesheet' href='https://www.gstatic.com/external_hosted/materialize/all_styles-bundle.css'>
     <link type='text/css' href='/css/navbar.css' rel='stylesheet'>
     <link type='text/css' href='/css/show_table.css' rel='stylesheet'>
+    <script src='/js/analytics.js' type='text/javascript'></script>
     <script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>
     <script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>
     <script src='https://www.gstatic.com/external_hosted/materialize/materialize.min.js'></script>
     <script src='https://www.gstatic.com/external_hosted/moment/min/moment-with-locales.min.js'></script>
     <script type='text/javascript'>
+      if (${analytics_id}) analytics_init(${analytics_id});
       google.charts.load('current', {'packages':['table', 'corechart']});
       google.charts.setOnLoadCallback(drawGridTable);
       google.charts.setOnLoadCallback(drawProfilingTable);