Merge "vts framework-side changes for wifi target-side tests"
diff --git a/proto/VtsReportMessage.proto b/proto/VtsReportMessage.proto
index f9dca10..6aa3694 100644
--- a/proto/VtsReportMessage.proto
+++ b/proto/VtsReportMessage.proto
@@ -55,6 +55,8 @@
   VTS_PROFILING_TYPE_TIMESTAMP = 1;
   // for multiple single-type samples with labels.
   VTS_PROFILING_TYPE_LABELED_VECTOR = 2;
+  // for multiple single-type samples without labels.
+  VTS_PROFILING_TYPE_UNLABELED_VECTOR = 3;
 }
 
 // To specify a call flow event.
diff --git a/proto/VtsReportMessage_pb2.py b/proto/VtsReportMessage_pb2.py
index 758d336..78925bd 100644
--- a/proto/VtsReportMessage_pb2.py
+++ b/proto/VtsReportMessage_pb2.py
@@ -14,7 +14,7 @@
 DESCRIPTOR = _descriptor.FileDescriptor(
   name='VtsReportMessage.proto',
   package='android.vts',
-  serialized_pb='\n\x16VtsReportMessage.proto\x12\x0b\x61ndroid.vts\"\xe0\x01\n\x18\x41ndroidDeviceInfoMessage\x12\x14\n\x0cproduct_type\x18\x01 \x01(\x0c\x12\x17\n\x0fproduct_variant\x18\x02 \x01(\x0c\x12\x14\n\x0c\x62uild_flavor\x18\x0b \x01(\x0c\x12\x10\n\x08\x62uild_id\x18\x0c \x01(\x0c\x12\x0e\n\x06\x62ranch\x18\x15 \x01(\x0c\x12\x13\n\x0b\x62uild_alias\x18\x16 \x01(\x0c\x12\x11\n\tapi_level\x18\x1f \x01(\x0c\x12\x10\n\x08\x61\x62i_name\x18\x33 \x01(\x0c\x12\x13\n\x0b\x61\x62i_bitness\x18\x34 \x01(\x0c\x12\x0e\n\x06serial\x18\x65 \x01(\x0c\"g\n\x10\x41ndroidBuildInfo\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x0b \x01(\x0c\x12\x12\n\nbuild_type\x18\x0c \x01(\x0c\x12\x0e\n\x06\x62ranch\x18\r \x01(\x0c\x12\x15\n\rbuild_summary\x18\x15 \x01(\x0c\"\x1f\n\x0bVtsHostInfo\x12\x10\n\x08hostname\x18\x01 \x01(\x0c\"\xab\x02\n\x15TestCaseReportMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x30\n\x0btest_result\x18\x0b \x01(\x0e\x32\x1b.android.vts.TestCaseResult\x12\x17\n\x0fstart_timestamp\x18\x15 \x01(\x03\x12\x15\n\rend_timestamp\x18\x16 \x01(\x03\x12\x34\n\x08\x63overage\x18\x1f \x03(\x0b\x32\".android.vts.CoverageReportMessage\x12\x36\n\tprofiling\x18) \x03(\x0b\x32#.android.vts.ProfilingReportMessage\x12\x34\n\x08systrace\x18* \x03(\x0b\x32\".android.vts.SystraceReportMessage\"\xa0\x02\n\x16ProfilingReportMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12+\n\x04type\x18\x02 \x01(\x0e\x32\x1d.android.vts.VtsProfilingType\x12@\n\x0fregression_mode\x18\x03 \x01(\x0e\x32\'.android.vts.VtsProfilingRegressionMode\x12\x17\n\x0fstart_timestamp\x18\x0b \x01(\x03\x12\x15\n\rend_timestamp\x18\x0c \x01(\x03\x12\r\n\x05label\x18\x15 \x03(\x0c\x12\r\n\x05value\x18\x16 \x03(\x03\x12\x14\n\x0cx_axis_label\x18\x1f \x01(\x0c\x12\x14\n\x0cy_axis_label\x18  \x01(\x0c\x12\x0f\n\x07options\x18) \x03(\x0c\";\n\x15SystraceReportMessage\x12\x14\n\x0cprocess_name\x18\x01 \x01(\x0c\x12\x0c\n\x04html\x18\x0b \x03(\x0c\"\xe5\x01\n\x15\x43overageReportMessage\x12\x11\n\tfile_path\x18\x0b \x01(\x0c\x12\x14\n\x0cproject_name\x18\x0c \x01(\x0c\x12\x10\n\x08revision\x18\r \x01(\x0c\x12\x1c\n\x14line_coverage_vector\x18\x17 \x03(\x05\x12\x18\n\x10total_line_count\x18\x65 \x01(\x05\x12\x1a\n\x12\x63overed_line_count\x18\x66 \x01(\x05\x12\x14\n\x08\x64ir_path\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x15\n\tfile_name\x18\x02 \x01(\x0c\x42\x02\x18\x01\x12\x10\n\x04html\x18\x03 \x01(\x0c\x42\x02\x18\x01\"\xa3\x04\n\x11TestReportMessage\x12\x12\n\ntest_suite\x18\x01 \x01(\x0c\x12\x0c\n\x04test\x18\x02 \x01(\x0c\x12+\n\ttest_type\x18\x03 \x01(\x0e\x32\x18.android.vts.VtsTestType\x12:\n\x0b\x64\x65vice_info\x18\x04 \x03(\x0b\x32%.android.vts.AndroidDeviceInfoMessage\x12\x31\n\nbuild_info\x18\x05 \x01(\x0b\x32\x1d.android.vts.AndroidBuildInfo\x12\x18\n\x10subscriber_email\x18\x06 \x03(\x0c\x12+\n\thost_info\x18\x07 \x01(\x0b\x32\x18.android.vts.VtsHostInfo\x12\x35\n\ttest_case\x18\x0b \x03(\x0b\x32\".android.vts.TestCaseReportMessage\x12\x36\n\tprofiling\x18\x15 \x03(\x0b\x32#.android.vts.ProfilingReportMessage\x12\x34\n\x08systrace\x18\x16 \x03(\x0b\x32\".android.vts.SystraceReportMessage\x12\x17\n\x0fstart_timestamp\x18\x65 \x01(\x03\x12\x15\n\rend_timestamp\x18\x66 \x01(\x03\x12\x34\n\x08\x63overage\x18g \x03(\x0b\x32\".android.vts.CoverageReportMessage*\xb3\x01\n\x0eTestCaseResult\x12\x12\n\x0eUNKNOWN_RESULT\x10\x00\x12\x19\n\x15TEST_CASE_RESULT_PASS\x10\x01\x12\x19\n\x15TEST_CASE_RESULT_FAIL\x10\x02\x12\x19\n\x15TEST_CASE_RESULT_SKIP\x10\x03\x12\x1e\n\x1aTEST_CASE_RESULT_EXCEPTION\x10\x04\x12\x1c\n\x18TEST_CASE_RESULT_TIMEOUT\x10\x05*\x9c\x01\n\x0bVtsTestType\x12\x18\n\x14UNKNOWN_VTS_TESTTYPE\x10\x00\x12\x1e\n\x1aVTS_HOST_DRIVEN_STRUCTURAL\x10\x01\x12\x1b\n\x17VTS_HOST_DRIVEN_FUZZING\x10\x02\x12\x19\n\x15VTS_TARGET_SIDE_GTEST\x10\x03\x12\x1b\n\x17VTS_TARGET_SIDE_FUZZING\x10\x04*\xa3\x01\n\x1aVtsProfilingRegressionMode\x12\x1b\n\x17UNKNOWN_REGRESSION_MODE\x10\x00\x12 \n\x1cVTS_REGRESSION_MODE_DISABLED\x10\x01\x12\"\n\x1eVTS_REGRESSION_MODE_INCREASING\x10\x02\x12\"\n\x1eVTS_REGRESSION_MODE_DECREASING\x10\x03*{\n\x10VtsProfilingType\x12\x1e\n\x1aUNKNOWN_VTS_PROFILING_TYPE\x10\x00\x12 \n\x1cVTS_PROFILING_TYPE_TIMESTAMP\x10\x01\x12%\n!VTS_PROFILING_TYPE_LABELED_VECTOR\x10\x02\x42)\n\x15\x63om.android.vts.protoB\x10VtsReportMessage')
+  serialized_pb='\n\x16VtsReportMessage.proto\x12\x0b\x61ndroid.vts\"\xe0\x01\n\x18\x41ndroidDeviceInfoMessage\x12\x14\n\x0cproduct_type\x18\x01 \x01(\x0c\x12\x17\n\x0fproduct_variant\x18\x02 \x01(\x0c\x12\x14\n\x0c\x62uild_flavor\x18\x0b \x01(\x0c\x12\x10\n\x08\x62uild_id\x18\x0c \x01(\x0c\x12\x0e\n\x06\x62ranch\x18\x15 \x01(\x0c\x12\x13\n\x0b\x62uild_alias\x18\x16 \x01(\x0c\x12\x11\n\tapi_level\x18\x1f \x01(\x0c\x12\x10\n\x08\x61\x62i_name\x18\x33 \x01(\x0c\x12\x13\n\x0b\x61\x62i_bitness\x18\x34 \x01(\x0c\x12\x0e\n\x06serial\x18\x65 \x01(\x0c\"g\n\x10\x41ndroidBuildInfo\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x0b \x01(\x0c\x12\x12\n\nbuild_type\x18\x0c \x01(\x0c\x12\x0e\n\x06\x62ranch\x18\r \x01(\x0c\x12\x15\n\rbuild_summary\x18\x15 \x01(\x0c\"\x1f\n\x0bVtsHostInfo\x12\x10\n\x08hostname\x18\x01 \x01(\x0c\"\xab\x02\n\x15TestCaseReportMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x30\n\x0btest_result\x18\x0b \x01(\x0e\x32\x1b.android.vts.TestCaseResult\x12\x17\n\x0fstart_timestamp\x18\x15 \x01(\x03\x12\x15\n\rend_timestamp\x18\x16 \x01(\x03\x12\x34\n\x08\x63overage\x18\x1f \x03(\x0b\x32\".android.vts.CoverageReportMessage\x12\x36\n\tprofiling\x18) \x03(\x0b\x32#.android.vts.ProfilingReportMessage\x12\x34\n\x08systrace\x18* \x03(\x0b\x32\".android.vts.SystraceReportMessage\"\xa0\x02\n\x16ProfilingReportMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12+\n\x04type\x18\x02 \x01(\x0e\x32\x1d.android.vts.VtsProfilingType\x12@\n\x0fregression_mode\x18\x03 \x01(\x0e\x32\'.android.vts.VtsProfilingRegressionMode\x12\x17\n\x0fstart_timestamp\x18\x0b \x01(\x03\x12\x15\n\rend_timestamp\x18\x0c \x01(\x03\x12\r\n\x05label\x18\x15 \x03(\x0c\x12\r\n\x05value\x18\x16 \x03(\x03\x12\x14\n\x0cx_axis_label\x18\x1f \x01(\x0c\x12\x14\n\x0cy_axis_label\x18  \x01(\x0c\x12\x0f\n\x07options\x18) \x03(\x0c\";\n\x15SystraceReportMessage\x12\x14\n\x0cprocess_name\x18\x01 \x01(\x0c\x12\x0c\n\x04html\x18\x0b \x03(\x0c\"\xe5\x01\n\x15\x43overageReportMessage\x12\x11\n\tfile_path\x18\x0b \x01(\x0c\x12\x14\n\x0cproject_name\x18\x0c \x01(\x0c\x12\x10\n\x08revision\x18\r \x01(\x0c\x12\x1c\n\x14line_coverage_vector\x18\x17 \x03(\x05\x12\x18\n\x10total_line_count\x18\x65 \x01(\x05\x12\x1a\n\x12\x63overed_line_count\x18\x66 \x01(\x05\x12\x14\n\x08\x64ir_path\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x15\n\tfile_name\x18\x02 \x01(\x0c\x42\x02\x18\x01\x12\x10\n\x04html\x18\x03 \x01(\x0c\x42\x02\x18\x01\"\xa3\x04\n\x11TestReportMessage\x12\x12\n\ntest_suite\x18\x01 \x01(\x0c\x12\x0c\n\x04test\x18\x02 \x01(\x0c\x12+\n\ttest_type\x18\x03 \x01(\x0e\x32\x18.android.vts.VtsTestType\x12:\n\x0b\x64\x65vice_info\x18\x04 \x03(\x0b\x32%.android.vts.AndroidDeviceInfoMessage\x12\x31\n\nbuild_info\x18\x05 \x01(\x0b\x32\x1d.android.vts.AndroidBuildInfo\x12\x18\n\x10subscriber_email\x18\x06 \x03(\x0c\x12+\n\thost_info\x18\x07 \x01(\x0b\x32\x18.android.vts.VtsHostInfo\x12\x35\n\ttest_case\x18\x0b \x03(\x0b\x32\".android.vts.TestCaseReportMessage\x12\x36\n\tprofiling\x18\x15 \x03(\x0b\x32#.android.vts.ProfilingReportMessage\x12\x34\n\x08systrace\x18\x16 \x03(\x0b\x32\".android.vts.SystraceReportMessage\x12\x17\n\x0fstart_timestamp\x18\x65 \x01(\x03\x12\x15\n\rend_timestamp\x18\x66 \x01(\x03\x12\x34\n\x08\x63overage\x18g \x03(\x0b\x32\".android.vts.CoverageReportMessage*\xb3\x01\n\x0eTestCaseResult\x12\x12\n\x0eUNKNOWN_RESULT\x10\x00\x12\x19\n\x15TEST_CASE_RESULT_PASS\x10\x01\x12\x19\n\x15TEST_CASE_RESULT_FAIL\x10\x02\x12\x19\n\x15TEST_CASE_RESULT_SKIP\x10\x03\x12\x1e\n\x1aTEST_CASE_RESULT_EXCEPTION\x10\x04\x12\x1c\n\x18TEST_CASE_RESULT_TIMEOUT\x10\x05*\x9c\x01\n\x0bVtsTestType\x12\x18\n\x14UNKNOWN_VTS_TESTTYPE\x10\x00\x12\x1e\n\x1aVTS_HOST_DRIVEN_STRUCTURAL\x10\x01\x12\x1b\n\x17VTS_HOST_DRIVEN_FUZZING\x10\x02\x12\x19\n\x15VTS_TARGET_SIDE_GTEST\x10\x03\x12\x1b\n\x17VTS_TARGET_SIDE_FUZZING\x10\x04*\xa3\x01\n\x1aVtsProfilingRegressionMode\x12\x1b\n\x17UNKNOWN_REGRESSION_MODE\x10\x00\x12 \n\x1cVTS_REGRESSION_MODE_DISABLED\x10\x01\x12\"\n\x1eVTS_REGRESSION_MODE_INCREASING\x10\x02\x12\"\n\x1eVTS_REGRESSION_MODE_DECREASING\x10\x03*\xa4\x01\n\x10VtsProfilingType\x12\x1e\n\x1aUNKNOWN_VTS_PROFILING_TYPE\x10\x00\x12 \n\x1cVTS_PROFILING_TYPE_TIMESTAMP\x10\x01\x12%\n!VTS_PROFILING_TYPE_LABELED_VECTOR\x10\x02\x12\'\n#VTS_PROFILING_TYPE_UNLABELED_VECTOR\x10\x03\x42)\n\x15\x63om.android.vts.protoB\x10VtsReportMessage')
 
 _TESTCASERESULT = _descriptor.EnumDescriptor(
   name='TestCaseResult',
@@ -136,11 +136,15 @@
       name='VTS_PROFILING_TYPE_LABELED_VECTOR', index=2, number=2,
       options=None,
       type=None),
+    _descriptor.EnumValueDescriptor(
+      name='VTS_PROFILING_TYPE_UNLABELED_VECTOR', index=3, number=3,
+      options=None,
+      type=None),
   ],
   containing_type=None,
   options=None,
-  serialized_start=2347,
-  serialized_end=2470,
+  serialized_start=2348,
+  serialized_end=2512,
 )
 
 VtsProfilingType = enum_type_wrapper.EnumTypeWrapper(_VTSPROFILINGTYPE)
@@ -162,6 +166,7 @@
 UNKNOWN_VTS_PROFILING_TYPE = 0
 VTS_PROFILING_TYPE_TIMESTAMP = 1
 VTS_PROFILING_TYPE_LABELED_VECTOR = 2
+VTS_PROFILING_TYPE_UNLABELED_VECTOR = 3
 
 
 
diff --git a/runners/host/base_test_with_webdb.py b/runners/host/base_test_with_webdb.py
index 450871a..e13bf59 100644
--- a/runners/host/base_test_with_webdb.py
+++ b/runners/host/base_test_with_webdb.py
@@ -415,21 +415,23 @@
         self._profiling[name].end_timestamp = self.GetTimestamp()
         return True
 
-    def AddProfilingDataLabeledVector(
+    def AddProfilingDataVector(
             self,
             name,
             labels,
             values,
+            data_type,
             options=[],
             x_axis_label="x-axis",
             y_axis_label="y-axis",
             regression_mode=ReportMsg.VTS_REGRESSION_MODE_INCREASING):
-        """Adds the profiling data in order to upload to the web DB.
+        """Adds the vector profiling data in order to upload to the web DB.
 
         Args:
             name: string, profiling point name.
-            labels: a list of labels.
-            values: a list of values.
+            labels: a list or set of labels.
+            values: a list or set of values where each value is an integer.
+            data_type: profiling data type.
             options: a set of options.
             x-axis_label: string, the x-axis label title for a graph plot.
             y-axis_label: string, the y-axis label title for a graph plot.
@@ -446,23 +448,83 @@
 
         self._profiling[name] = self._report_msg.profiling.add()
         self._profiling[name].name = name
-        self._profiling[
-            name].type = ReportMsg.VTS_PROFILING_TYPE_LABELED_VECTOR
+        self._profiling[name].type = data_type
         self._profiling[name].regression_mode = regression_mode
-        for label, value in zip(labels, values):
-            self._profiling[name].label.append(label)
-            self._profiling[name].value.append(value)
+        if labels:
+            self._profiling[name].label.extend(labels)
+        self._profiling[name].value.extend(values)
         self._profiling[name].x_axis_label = x_axis_label
         self._profiling[name].y_axis_label = y_axis_label
-        for option in options:
-            self._profiling[name].options.append(option)
+        self._profiling[name].options.extend(options)
 
-    def AddProfilingDataLabeledPoint(self, name, value):
-        """Adds labeled point type profiling data for uploading to the web DB.
+    def AddProfilingDataLabeledVector(
+            self,
+            name,
+            labels,
+            values,
+            options=[],
+            x_axis_label="x-axis",
+            y_axis_label="y-axis",
+            regression_mode=ReportMsg.VTS_REGRESSION_MODE_INCREASING):
+        """Adds the labeled vector profiling data in order to upload to the web DB.
 
         Args:
             name: string, profiling point name.
-            value: int, the value.
+            labels: a list or set of labels.
+            values: a list or set of values where each value is an integer.
+            options: a set of options.
+            x-axis_label: string, the x-axis label title for a graph plot.
+            y-axis_label: string, the y-axis label title for a graph plot.
+            regression_mode: specifies the direction of change which indicates
+                             performance regression.
+        """
+        self.AddProfilingDataVector(name, labels, values,
+                                    ReportMsg.VTS_PROFILING_TYPE_LABELED_VECTOR,
+                                    options, x_axis_label, y_axis_label,
+                                    regression_mode)
+
+    def AddProfilingDataUnlabeledVector(
+            self,
+            name,
+            values,
+            options=[],
+            x_axis_label="x-axis",
+            y_axis_label="y-axis",
+            regression_mode=ReportMsg.VTS_REGRESSION_MODE_INCREASING):
+        """Adds the unlabeled vector profiling data in order to upload to the web DB.
+
+        Args:
+            name: string, profiling point name.
+            values: a list or set of values where each value is an integer.
+            options: a set of options.
+            x-axis_label: string, the x-axis label title for a graph plot.
+            y-axis_label: string, the y-axis label title for a graph plot.
+            regression_mode: specifies the direction of change which indicates
+                             performance regression.
+        """
+        self.AddProfilingDataVector(name, None, values,
+                                    ReportMsg.VTS_PROFILING_TYPE_UNLABELED_VECTOR,
+                                    options, x_axis_label, y_axis_label,
+                                    regression_mode)
+
+    def AddProfilingDataTimestamp(
+            self,
+            name,
+            value,
+            options=[],
+            x_axis_label="x-axis",
+            y_axis_label="y-axis",
+            regression_mode=ReportMsg.VTS_REGRESSION_MODE_INCREASING):
+        """Adds the named point type profiling data in order to upload to the web DB.
+
+        Args:
+            name: string, profiling point name.
+            value: integer, the value.
+            options: a set of options.
+            x-axis_label: string, the x-axis label title for a graph plot.
+            y-axis_label: string, the y-axis label title for a graph plot.
+            regression_mode: specifies the direction of change which indicates
+                             performance regression.
         """
         if not getattr(self, self.USE_GAE_DB, False):
             logging.error("'use_gae_db' config is not True.")
@@ -474,9 +536,12 @@
         self._profiling[name] = self._report_msg.profiling.add()
         self._profiling[name].name = name
         self._profiling[name].type = ReportMsg.VTS_PROFILING_TYPE_TIMESTAMP
+        self._profiling[name].regression_mode = regression_mode
         self._profiling[name].start_timestamp = 0
         self._profiling[name].end_timestamp = value
-        return True
+        self._profiling[name].x_axis_label = x_axis_label
+        self._profiling[name].y_axis_label = y_axis_label
+        self._profiling[name].options.extend(options)
 
     def IsCoverageConfigSpecified(self):
         """Determines if the config file specifies modules for coverage.
@@ -651,8 +716,6 @@
         """
         merged_profiling_data = profiling_utils.VTSProfilingData()
         for data in self._profiling_data:
-            if data.name:
-                merged_profiling_data.name = data.name
             for item in data.options:
                 merged_profiling_data.options.add(item)
             for api, latences in data.values.items():
@@ -661,23 +724,9 @@
                 else:
                     merged_profiling_data.values[api] = latences
         for api, latencies in merged_profiling_data.values.items():
-            if latencies:
-                merged_profiling_data.labels.append(api)
-                merged_profiling_data.aggregated_values["max"].append(
-                    max(latencies))
-                merged_profiling_data.aggregated_values["min"].append(
-                    min(latencies))
-                merged_profiling_data.aggregated_values["avg"].append(
-                    sum(latencies) / len(latencies))
-        for tag in [_MAX, _MIN, _AVG]:
-            if merged_profiling_data.name is None:
-                name = tag
-            else:
-                name = merged_profiling_data.name + "_" + tag
-            self.AddProfilingDataLabeledVector(
-                name,
-                merged_profiling_data.labels,
-                merged_profiling_data.aggregated_values[tag],
+            self.AddProfilingDataUnlabeledVector(
+                api,
+                latencies,
                 merged_profiling_data.options,
-                x_axis_label="API name",
-                y_axis_label="API processing latency (nana secs)")
+                x_axis_label="API processing latency (nano secs)",
+                y_axis_label="Frequency")
diff --git a/tools/vts-tradefed/res/config/vts-serving-staging-hal-hidl-profiling.xml b/tools/vts-tradefed/res/config/vts-serving-staging-hal-hidl-profiling.xml
index b481f4e..956534c 100644
--- a/tools/vts-tradefed/res/config/vts-serving-staging-hal-hidl-profiling.xml
+++ b/tools/vts-tradefed/res/config/vts-serving-staging-hal-hidl-profiling.xml
@@ -27,6 +27,7 @@
     <option name="compatibility:include-filter" value="VrHidlTargetProfilingTest" />
     <option name="compatibility:include-filter" value="HalMemtrackHidlTargetProfilingTest" />
     <option name="compatibility:include-filter" value="HalGatekeeperHidlTargetBasicProfilingTest" />
-    <option name="compatibility:include-filter" value="HalBootHidlTargetProfilingTest" />
+    <!--TODO: re-enable when VTS can handle device/hal incompatibility -->
+    <!-- <option name="compatibility:include-filter" value="HalBootHidlTargetProfilingTest" /> -->
     <template-include name="reporters" default="basic-reporters" />
 </configuration>
diff --git a/utils/python/cpu/cpu_frequency_scaling.py b/utils/python/cpu/cpu_frequency_scaling.py
index a292d88..f29f37b 100644
--- a/utils/python/cpu/cpu_frequency_scaling.py
+++ b/utils/python/cpu/cpu_frequency_scaling.py
@@ -104,9 +104,11 @@
                 ["cat /sys/devices/system/cpu/cpu%s/cpufreq/scaling_max_freq" % cpu_no,
                  "cat /sys/devices/system/cpu/cpu%s/cpufreq/scaling_cur_freq" % cpu_no])
             asserts.assertEqual(2, len(results[const.STDOUT]))
-            asserts.assertFalse(
-                any(results[const.EXIT_CODE]),
-                "Can't check the current and/or max CPU frequency.")
+            if any(results[const.EXIT_CODE]):
+                logging.error("Can't check the current and/or max CPU frequency.")
+                logging.error("Stderr for scaling_max_freq: %s", results[const.STDERR][0])
+                logging.error("Stderr for scaling_cur_freq: %s", results[const.STDERR][1])
+                return True
             configurable_max_frequency = results[const.STDOUT][0].strip()
             current_frequency = results[const.STDOUT][1].strip()
             if configurable_max_frequency != current_frequency:
diff --git a/utils/python/mirror/hal_mirror.py b/utils/python/mirror/hal_mirror.py
index cba4220..c8e7146 100644
--- a/utils/python/mirror/hal_mirror.py
+++ b/utils/python/mirror/hal_mirror.py
@@ -159,7 +159,7 @@
                     target_component_name=None,
                     target_basepaths=_DEFAULT_TARGET_BASE_PATHS,
                     handler_name=None,
-                    hw_binder_service_name=_DEFAULT_TARGET_BASE_PATHS,
+                    hw_binder_service_name=_DEFAULT_HWBINDER_SERVICE,
                     bits=64):
         """Initiates a handler for a particular HIDL HAL.
 
diff --git a/utils/python/profiling/profiling_utils.py b/utils/python/profiling/profiling_utils.py
index 29537f4..deb423a 100644
--- a/utils/python/profiling/profiling_utils.py
+++ b/utils/python/profiling/profiling_utils.py
@@ -59,19 +59,13 @@
     """Class to store the VTS profiling data.
 
     Attributes:
-        name: A string to describe the profiling data. e.g. server_side_latency.
-        labels: A list of profiling data labels. e.g. a list of api names.
         values: A dict that stores the profiling data. e.g. latencies of each api.
-        aggregated_values: A dict that stores the aggregated profiling data.
         options: A set of strings where each string specifies an associated
                  option (which is the form of 'key=value').
     """
 
     def __init__(self):
-        self.name = None
         self.values = {}
-        self.labels = []
-        self.aggregated_values = {"avg": [], "max": [], "min": []}
         self.options = set()
 
 
@@ -143,12 +137,6 @@
             latencies.append(
                 long(time_stamps[index]) - long(time_stamps[index - 1]))
         profiling_data.values[api] = latencies
-    for api, latencies in profiling_data.values.items():
-        if latencies:
-            profiling_data.labels.append(api)
-            profiling_data.aggregated_values["max"].append(max(latencies))
-            profiling_data.aggregated_values["min"].append(min(latencies))
-            profiling_data.aggregated_values["avg"].append(sum(latencies) / len(latencies))
     return profiling_data
 
 
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/proto/VtsReportMessage.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/proto/VtsReportMessage.java
index 55078a7..d8558f1 100644
--- a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/proto/VtsReportMessage.java
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/proto/VtsReportMessage.java
@@ -392,6 +392,14 @@
      * </pre>
      */
     VTS_PROFILING_TYPE_LABELED_VECTOR(2, 2),
+    /**
+     * <code>VTS_PROFILING_TYPE_UNLABELED_VECTOR = 3;</code>
+     *
+     * <pre>
+     * for multiple single-type samples without labels.
+     * </pre>
+     */
+    VTS_PROFILING_TYPE_UNLABELED_VECTOR(3, 3),
     ;
 
     /**
@@ -414,6 +422,14 @@
      * </pre>
      */
     public static final int VTS_PROFILING_TYPE_LABELED_VECTOR_VALUE = 2;
+    /**
+     * <code>VTS_PROFILING_TYPE_UNLABELED_VECTOR = 3;</code>
+     *
+     * <pre>
+     * for multiple single-type samples without labels.
+     * </pre>
+     */
+    public static final int VTS_PROFILING_TYPE_UNLABELED_VECTOR_VALUE = 3;
 
 
     public final int getNumber() { return value; }
@@ -423,6 +439,7 @@
         case 0: return UNKNOWN_VTS_PROFILING_TYPE;
         case 1: return VTS_PROFILING_TYPE_TIMESTAMP;
         case 2: return VTS_PROFILING_TYPE_LABELED_VECTOR;
+        case 3: return VTS_PROFILING_TYPE_UNLABELED_VECTOR;
         default: return null;
       }
     }
@@ -13205,11 +13222,12 @@
       "essionMode\022\033\n\027UNKNOWN_REGRESSION_MODE\020\000\022" +
       " \n\034VTS_REGRESSION_MODE_DISABLED\020\001\022\"\n\036VTS" +
       "_REGRESSION_MODE_INCREASING\020\002\022\"\n\036VTS_REG" +
-      "RESSION_MODE_DECREASING\020\003*{\n\020VtsProfilin" +
-      "gType\022\036\n\032UNKNOWN_VTS_PROFILING_TYPE\020\000\022 \n",
-      "\034VTS_PROFILING_TYPE_TIMESTAMP\020\001\022%\n!VTS_P" +
-      "ROFILING_TYPE_LABELED_VECTOR\020\002B)\n\025com.an" +
-      "droid.vts.protoB\020VtsReportMessage"
+      "RESSION_MODE_DECREASING\020\003*\244\001\n\020VtsProfili" +
+      "ngType\022\036\n\032UNKNOWN_VTS_PROFILING_TYPE\020\000\022 ",
+      "\n\034VTS_PROFILING_TYPE_TIMESTAMP\020\001\022%\n!VTS_" +
+      "PROFILING_TYPE_LABELED_VECTOR\020\002\022\'\n#VTS_P" +
+      "ROFILING_TYPE_UNLABELED_VECTOR\020\003B)\n\025com." +
+      "android.vts.protoB\020VtsReportMessage"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
       new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
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 4e4d3e7..c26c753 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
@@ -23,6 +23,7 @@
 import com.android.vts.util.BigtableHelper;
 import com.android.vts.util.Graph;
 import com.android.vts.util.GraphSerializer;
+import com.android.vts.util.Histogram;
 import com.android.vts.util.LineGraph;
 import com.android.vts.util.PerformanceUtil;
 import org.apache.hadoop.hbase.TableName;
@@ -58,31 +59,52 @@
 
     private static final String HIDL_HAL_OPTION = "hidl_hal_mode";
     private static final String[] splitKeysArray = new String[]{HIDL_HAL_OPTION};
-    private static final Set<String> splitKeySet = new HashSet<String>(Arrays.asList(splitKeysArray));
+    private static final Set<String> splitKeySet =
+            new HashSet<String>(Arrays.asList(splitKeysArray));
     private static final String PROFILING_DATA_ALERT = "No profiling data was found.";
 
 
     private static final long MILLI_TO_MICRO = 1000;  // conversion factor from milli to micro units
 
     /**
-     * Process a profiling report message and determine which graph to insert the point into.
+     * Process a profiling report message and determine which line graph to insert the point into.
      * @param profilingReportMessage The profiling report to process.
      * @param idString The ID derived from the test run to identify the profiling report.
      * @param graphMap A map from graph name to Graph object.
      */
-    private static void processLabeledListReport(
+    private static void processLineGraphReport(
             ProfilingReportMessage profilingReportMessage, String idString,
             Map<String, Graph> graphMap) {
         if (profilingReportMessage.getLabelList().size() == 0 ||
             profilingReportMessage.getLabelList().size() !=
             profilingReportMessage.getValueList().size()) return;
-        String name = PerformanceUtil.getOptionKeys(profilingReportMessage.getOptionsList(), splitKeySet);
+        String name = PerformanceUtil.getOptionKeys(profilingReportMessage.getOptionsList(),
+                                                    splitKeySet);
         if (!graphMap.containsKey(name)) {
             graphMap.put(name, new LineGraph(name));
         }
         graphMap.get(name).addData(idString, profilingReportMessage);
     }
 
+    /**
+     * Process a profiling report message and determine which histogram to insert the point into.
+     * @param profilingReportMessage The profiling report to process.
+     * @param graphMap A map from graph name to Graph object.
+     */
+    private static void processHistogramReport(
+            ProfilingReportMessage profilingReportMessage, String idString,
+            Map<String, Graph> graphMap) {
+        if (profilingReportMessage.getValueList().size() == 0 &&
+            profilingReportMessage.getStartTimestamp() >= profilingReportMessage.getEndTimestamp())
+            return;
+        String name = PerformanceUtil.getOptionKeys(
+                profilingReportMessage.getOptionsList(), splitKeySet);
+        if (!graphMap.containsKey(name)) {
+            graphMap.put(name, new Histogram(name));
+        }
+        graphMap.get(name).addData(idString, profilingReportMessage);
+    }
+
     @Override
     public void doGetHandler(HttpServletRequest request, HttpServletResponse response)
             throws IOException {
@@ -105,9 +127,6 @@
         tableName = TableName.valueOf(TABLE_PREFIX + request.getParameter("testName"));
         table = BigtableHelper.getTable(tableName);
 
-        // This list holds the values for all profiling points.
-        List<Double> profilingPointValuesList = new ArrayList<>();
-
         // Set of device names
         Set<String> deviceSet = new HashSet<String>();
 
@@ -124,7 +143,8 @@
 
             AndroidDeviceInfoMessage firstDeviceInfo = testReportMessage.getDeviceInfoList().get(0);
             String firstDeviceBuildId = firstDeviceInfo.getBuildId().toStringUtf8();
-            String firstDeviceType = firstDeviceInfo.getProductVariant().toStringUtf8().toLowerCase();
+            String firstDeviceType =
+                    firstDeviceInfo.getProductVariant().toStringUtf8().toLowerCase();
             String idString = firstDeviceType + " (" + firstDeviceBuildId + ")";
             deviceSet.add(firstDeviceType);
             if (selectedDevice != null && !firstDeviceType.equals(selectedDevice)) continue;
@@ -138,9 +158,13 @@
                 switch(profilingReportMessage.getType()) {
                     case UNKNOWN_VTS_PROFILING_TYPE:
                     case VTS_PROFILING_TYPE_TIMESTAMP:
-                        continue;
+                        processHistogramReport(profilingReportMessage, idString, graphMap);
+                        break;
                     case VTS_PROFILING_TYPE_LABELED_VECTOR:
-                        processLabeledListReport(profilingReportMessage, idString, graphMap);
+                        processLineGraphReport(profilingReportMessage, idString, graphMap);
+                        break;
+                    case VTS_PROFILING_TYPE_UNLABELED_VECTOR:
+                        processHistogramReport(profilingReportMessage, idString, graphMap);
                         break;
                     default :
                         break;
@@ -155,12 +179,6 @@
             graphList.add(graphMap.get(name));
         }
 
-        // fill performance profiling array
-        double[] performanceProfilingValues = new double[profilingPointValuesList.size()];
-        for (int i = 0; i < profilingPointValuesList.size(); i++) {
-            performanceProfilingValues[i] = profilingPointValuesList.get(i).doubleValue();
-        }
-
         // sort devices list
         if (!deviceSet.contains(selectedDevice)) selectedDevice = null;
         String[] devices = deviceSet.toArray(new String[deviceSet.size()]);
@@ -172,7 +190,8 @@
         request.setAttribute("selectedDevice", selectedDevice);
         if (graphList.size() == 0) request.setAttribute("error", PROFILING_DATA_ALERT);
 
-        Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Graph.class, new GraphSerializer()).create();
+        Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(
+                Graph.class, new GraphSerializer()).create();
         request.setAttribute("graphs", gson.toJson(graphList));
 
         request.setAttribute("profilingPointName", profilingPointName);
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/Graph.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/Graph.java
index 6a9ea6b..e808451 100644
--- a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/Graph.java
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/Graph.java
@@ -27,6 +27,8 @@
 
     public static final String VALUE_KEY = "values";
     public static final String X_LABEL_KEY = "x_label";
+    public static final String Y_LABEL_KEY = "y_label";
+    public static final String IDS_KEY = "ids";
     public static final String NAME_KEY = "name";
     public static final String TYPE_KEY = "type";
 
@@ -48,6 +50,12 @@
     public abstract String getXLabel();
 
     /**
+     * Get the y axis label.
+     * @return The y axis label.
+     */
+    public abstract String getYLabel();
+
+    /**
      * Get the name of the graph.
      * @return The name of the graph.
      */
@@ -67,6 +75,7 @@
     public JsonObject toJson() {
         JsonObject json = new JsonObject();
         json.add(X_LABEL_KEY, new JsonPrimitive(getXLabel()));
+        json.add(Y_LABEL_KEY, new JsonPrimitive(getYLabel()));
         json.add(NAME_KEY, new JsonPrimitive(getName()));
         json.add(TYPE_KEY, new JsonPrimitive(getType().toString()));
         return json;
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/Histogram.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/Histogram.java
new file mode 100644
index 0000000..ab870ea
--- /dev/null
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/Histogram.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you
+ * may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.vts.util;
+
+import com.android.vts.proto.VtsReportMessage.ProfilingReportMessage;
+import com.google.common.primitives.Doubles;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.math3.stat.descriptive.rank.Percentile;
+
+/**
+ * Helper object for describing graph data.
+ */
+public class Histogram extends Graph {
+
+    public static final String PERCENTILES_KEY = "percentiles";
+    public static final String PERCENTILE_VALUES_KEY = "percentile_values";
+
+    private List<Double> values;
+    private List<String> ids;
+    private String xLabel;
+    private String yLabel;
+    private String name;
+    private GraphType type = GraphType.HISTOGRAM;
+
+    public Histogram(String name) {
+        this.name = name;
+        this.values = new ArrayList<>();
+        this.ids = new ArrayList<>();
+    }
+
+    /**
+     * Get the x axis label.
+     * @return The x axis label.
+     */
+    @Override
+    public String getXLabel() {
+        return xLabel;
+    }
+
+    /**
+     * Get the graph type.
+     * @return The graph type.
+     */
+    @Override
+    public GraphType getType() {
+        return type;
+    }
+
+    /**
+     * Get the name of the graph.
+     * @return The name of the graph.
+     */
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Get the y axis label.
+     * @return The y axis label.
+     */
+    @Override
+    public String getYLabel() {
+        return yLabel;
+    }
+
+    /**
+     * Add data to the graph.
+     * @param id The name of the graph.
+     * @param profilingReport The profiling report message containing data to add.
+     */
+    @Override
+    public void addData(String id, ProfilingReportMessage profilingReport) {
+        if (profilingReport.getValueList().size() == 0 &&
+            profilingReport.getStartTimestamp() >= profilingReport.getEndTimestamp()) return;
+        xLabel = profilingReport.getXAxisLabel().toStringUtf8();
+        yLabel = profilingReport.getYAxisLabel().toStringUtf8();
+        for (long value : profilingReport.getValueList()) {
+            values.add((double) value);
+            ids.add(id);
+        }
+        if (profilingReport.getStartTimestamp() != profilingReport.getEndTimestamp() &&
+            profilingReport.getStartTimestamp() < profilingReport.getEndTimestamp()) {
+            values.add((double) profilingReport.getEndTimestamp() -
+                       profilingReport.getStartTimestamp());
+            ids.add(id);
+        }
+    }
+
+    /**
+     * Serializes the graph to json format.
+     * @return A JsonElement object representing the graph object.
+     */
+    @Override
+    public JsonObject toJson() {
+        int[] percentiles = {1, 5, 10, 20, 25, 50, 75, 80, 90, 95, 99};
+        double[] percentileValues = new double[percentiles.length];
+        double[] valueList = Doubles.toArray(values);
+        for (int i = 0; i < percentiles.length; i++) {
+            percentileValues[i] =
+                Math.round(new Percentile().evaluate(valueList, percentiles[i]) * 1000d) / 1000d;
+        }
+        JsonObject json = super.toJson();
+        json.add(VALUE_KEY, new Gson().toJsonTree(values).getAsJsonArray());
+        json.add(PERCENTILES_KEY, new Gson().toJsonTree(percentiles).getAsJsonArray());
+        json.add(PERCENTILE_VALUES_KEY, new Gson().toJsonTree(percentileValues).getAsJsonArray());
+        json.add(IDS_KEY, new Gson().toJsonTree(ids).getAsJsonArray());
+        return json;
+    }
+}
+
diff --git a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/LineGraph.java b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/LineGraph.java
index e496ee0..a6ed50d 100644
--- a/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/LineGraph.java
+++ b/web/dashboard/appengine/servlet/src/main/java/com/android/vts/util/LineGraph.java
@@ -19,7 +19,6 @@
 import com.android.vts.proto.VtsReportMessage.ProfilingReportMessage;
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -30,9 +29,7 @@
  */
 public class LineGraph extends Graph {
 
-    private static final String Y_LABEL_KEY = "y_label";
-    private static final String IDS_KEY = "ids";
-    private static final String TICKS_KEY = "ticks";
+    public static final String TICKS_KEY = "ticks";
 
     private List<ProfilingReportMessage> profilingReports;
     private List<String> ids;
@@ -78,6 +75,7 @@
      * Get the y axis label.
      * @return The y axis label.
      */
+    @Override
     public String getYLabel() {
         return yLabel;
     }
@@ -131,7 +129,6 @@
             }
         }
         json.add(VALUE_KEY, new Gson().toJsonTree(lineGraphValues).getAsJsonArray());
-        json.add(Y_LABEL_KEY, new JsonPrimitive(getYLabel()));
         json.add(IDS_KEY, new Gson().toJsonTree(ids).getAsJsonArray());
         json.add(TICKS_KEY, new Gson().toJsonTree(axisTicks));
         return json;
diff --git a/web/dashboard/appengine/servlet/src/main/webapp/css/show_graph.css b/web/dashboard/appengine/servlet/src/main/webapp/css/show_graph.css
index 4de017a..44b2a2d 100644
--- a/web/dashboard/appengine/servlet/src/main/webapp/css/show_graph.css
+++ b/web/dashboard/appengine/servlet/src/main/webapp/css/show_graph.css
@@ -47,12 +47,23 @@
     padding-bottom: 25px;
 }
 
-.graph {
+.graph-wrapper {
     padding: 30px;
-    height: 550px;
 }
 
-#percentile-table-div {
-    margin-bottom: 20px;
+.graph {
+    height: 500px;
+    padding-bottom: 30px;
+}
+
+.percentile-table {
+    width: auto;
+    margin: auto;
+}
+
+.percentile-table td, th{
+    font-size: 12px;
+    text-align: center;
+    padding: 5px 10px;
 }
 
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 8cf8fc3..e9b8301 100644
--- a/web/dashboard/appengine/servlet/src/main/webapp/show_graph.jsp
+++ b/web/dashboard/appengine/servlet/src/main/webapp/show_graph.jsp
@@ -36,30 +36,43 @@
     <script type='text/javascript'>
       if (${analytics_id}) analytics_init(${analytics_id});
       google.charts.load('current', {packages:['corechart', 'table', 'line']});
-      google.charts.setOnLoadCallback(drawAllLineGraphs);
-      google.charts.setOnLoadCallback(function() {
-          $('.gradient').removeClass('gradient');
-      });
+      google.charts.setOnLoadCallback(drawAllGraphs);
 
+      ONE_DAY = 86400000000;
       MICRO_PER_MILLI = 1000;
 
       $(function() {
           $('select').material_select();
           var date = $('#date').datepicker({
-                  showAnim: 'slideDown',
-                  maxDate: new Date()
-                });
+              showAnim: 'slideDown',
+              maxDate: new Date()
+          });
           date.datepicker('setDate', new Date(${startTime} / MICRO_PER_MILLI));
           $('#load').click(load);
       });
 
-      function drawAllLineGraphs() {
+      // Draw all graphs.
+      function drawAllGraphs() {
           var graphs = ${graphs};
           graphs.forEach(function(graph) {
               if (graph.type == 'LINE_GRAPH') drawLineGraph(graph);
+              else if (graph.type == 'HISTOGRAM') drawHistogram(graph);
           });
       }
 
+     /**
+      * Draw a line graph.
+      *
+      * Args:
+      *     lineGraph: a JSON object containing the following fields:
+      *                - name: the name of the graph
+      *                - values: an array of numbers
+      *                - ticks: an array of strings to use as x-axis labels
+      *                - ids: an array of string labels for each point (e.g. the
+      *                       build info for the run that produced the point)
+      *                - x_label: the string label for the x axis
+      *                - y_label: the string label for the y axis
+      */
       function drawLineGraph(lineGraph) {
           var title = 'Performance';
           if (lineGraph.name) title += ' (' + lineGraph.name + ')';
@@ -70,7 +83,7 @@
               lineGraph.values[i].unshift(label);
           });
           var data = new google.visualization.DataTable();
-          data.addColumn('string', lineGraph.x_value);
+          data.addColumn('string', lineGraph.x_label);
           lineGraph.ids.forEach(function(id) {
               data.addColumn('number', id);
           });
@@ -82,14 +95,120 @@
             },
             legend: { position: 'none' }
           };
-          var container = $('<div class="row card center-align col s12 graph"></div>');
+          var container = $('<div class="row card center-align col s12 graph-wrapper"></div>');
           container.appendTo('#profiling-container');
-          var chart = new google.charts.Line(container[0]);
+          var chartDiv = $('<div class="col s12 graph"></div>');
+          chartDiv.appendTo(container);
+          var chart = new google.charts.Line(chartDiv[0]);
           chart.draw(data, options);
       }
 
+     /**
+      * Draw a histogram.
+      *
+      * Args:
+      *     hist: a JSON object containing the following fields:
+      *           - name: the name of the graph
+      *           - values: an array of numbers
+      *           - ids: an array of string labels for each point (e.g. the
+      *                  build info for the run that produced the point)
+      *           - x_label: the string label for the x axis
+      *           - y_label: the string label for the y axis
+      */
+      function drawHistogram(hist) {
+          test = hist;
+          var title = 'Performance';
+          if (hist.name) title += ' (' + hist.name + ')';
+          var values = hist.values;
+          var histogramData = values.map(function(d, i) {
+              return [hist.ids[i], d];
+          });
+          var min = Math.min.apply(null, values),
+              max = Math.max.apply(null, values);
+
+          var histogramTicks = new Array(10);
+          var delta = (max - min) / 10;
+          for (var i = 0; i <= 10; i++) {
+              histogramTicks[i] = Math.round(min + delta * i);
+          }
+
+          var data = google.visualization.arrayToDataTable(histogramData, true);
+
+          var options = {
+              title: title,
+              titleTextStyle: {
+                  color: '#757575',
+                  fontSize: 16,
+                  bold: false
+              },
+              legend: { position: 'none' },
+              colors: ['#4285F4'],
+              fontName: 'Roboto',
+              vAxis:{
+                  title: hist.y_label,
+                  titleTextStyle: {
+                      color: '#424242',
+                      fontSize: 12,
+                      italic: false
+                  },
+                  textStyle: {
+                      fontSize: 12,
+                      color: '#757575'
+                  },
+              },
+              hAxis: {
+                  ticks: histogramTicks,
+                  title: hist.x_label,
+                  textStyle: {
+                      fontSize: 12,
+                      color: '#757575'
+                  },
+                  titleTextStyle: {
+                      color: '#424242',
+                      fontSize: 12,
+                      italic: false
+                  }
+              },
+              bar: { gap: 0 },
+              histogram: {
+                  maxNumBuckets: 200,
+                  minValue: min * 0.95,
+                  maxValue: max * 1.05
+              },
+              chartArea: {
+                  width: '100%',
+                  top: 40,
+                  left: 50,
+                  height: '80%'
+              }
+          };
+          var container = $('<div class="row card col s12 graph-wrapper"></div>');
+          container.appendTo('#profiling-container');
+
+          var chartDiv = $('<div class="col s12 graph"></div>');
+          chartDiv.appendTo(container);
+          var chart = new google.visualization.Histogram(chartDiv[0]);
+          chart.draw(data, options);
+
+          var tableDiv = $('<div class="col s12"></div>');
+          tableDiv.appendTo(container);
+
+          var tableHtml = '<table class="percentile-table"><thead><tr>';
+          hist.percentiles.forEach(function(p) {
+              tableHtml += '<th data-field="id">' + p + '%</th>';
+          });
+          tableHtml += '</tr></thead><tbody><tr>';
+          hist.percentile_values.forEach(function(v) {
+              tableHtml += '<td>' + v + '</td>';
+          });
+          tableHtml += '</tbody></table>';
+          $(tableHtml).appendTo(tableDiv);
+      }
+
+      // Reload the page.
       function load() {
           var startTime = $('#date').datepicker('getDate').getTime();
+          startTime = startTime + (ONE_DAY / MICRO_PER_MILLI) - 1;
           var ctx = '${pageContext.request.contextPath}';
           var link = ctx + '/show_graph?profilingPoint=${profilingPointName}' +
               '&testName=${testName}' +