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);