1# 2# Copyright (C) 2017 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import getpass 18import logging 19import os 20import socket 21import time 22 23from vts.proto import VtsReportMessage_pb2 as ReportMsg 24from vts.runners.host import keys 25from vts.utils.python.web import dashboard_rest_client 26from vts.utils.python.web import feature_utils 27 28_PROFILING_POINTS = "profiling_points" 29 30 31class WebFeature(feature_utils.Feature): 32 """Feature object for web functionality. 33 34 Attributes: 35 enabled: boolean, True if systrace is enabled, False otherwise 36 report_msg: TestReportMessage, Proto summarizing the test run 37 current_test_report_msg: TestCaseReportMessage, Proto summarizing the current test case 38 rest_client: DashboardRestClient, client to which data will be posted 39 """ 40 41 _TOGGLE_PARAM = keys.ConfigKeys.IKEY_ENABLE_WEB 42 _REQUIRED_PARAMS = [ 43 keys.ConfigKeys.IKEY_DASHBOARD_POST_COMMAND, 44 keys.ConfigKeys.IKEY_SERVICE_JSON_PATH, 45 keys.ConfigKeys.KEY_TESTBED_NAME, 46 keys.ConfigKeys.IKEY_BUILD, 47 keys.ConfigKeys.IKEY_ANDROID_DEVICE, 48 keys.ConfigKeys.IKEY_ABI_NAME, 49 keys.ConfigKeys.IKEY_ABI_BITNESS 50 ] 51 _OPTIONAL_PARAMS = [] 52 53 def __init__(self, user_params): 54 """Initializes the web feature. 55 56 Parses the arguments and initializes the web functionality. 57 58 Args: 59 user_params: A dictionary from parameter name (String) to parameter value. 60 """ 61 self.ParseParameters( 62 toggle_param_name=self._TOGGLE_PARAM, 63 required_param_names=self._REQUIRED_PARAMS, 64 optional_param_names=self._OPTIONAL_PARAMS, 65 user_params=user_params) 66 if not self.enabled: 67 return 68 69 # Initialize the dashboard client 70 post_cmd = getattr(self, keys.ConfigKeys.IKEY_DASHBOARD_POST_COMMAND) 71 service_json_path = str(getattr(self, keys.ConfigKeys.IKEY_SERVICE_JSON_PATH)) 72 self.rest_client = dashboard_rest_client.DashboardRestClient( 73 post_cmd, service_json_path) 74 if not self.rest_client.Initialize(): 75 self.enabled = False 76 77 self.report_msg = ReportMsg.TestReportMessage() 78 self.report_msg.test = str( 79 getattr(self, keys.ConfigKeys.KEY_TESTBED_NAME)) 80 self.report_msg.test_type = ReportMsg.VTS_HOST_DRIVEN_STRUCTURAL 81 self.report_msg.start_timestamp = feature_utils.GetTimestamp() 82 self.report_msg.host_info.hostname = socket.gethostname() 83 84 android_devices = getattr(self, keys.ConfigKeys.IKEY_ANDROID_DEVICE, 85 None) 86 if not android_devices or not isinstance(android_devices, list): 87 logging.warn("android device information not available") 88 return 89 90 for device_spec in android_devices: 91 dev_info = self.report_msg.device_info.add() 92 for elem in [ 93 keys.ConfigKeys.IKEY_PRODUCT_TYPE, 94 keys.ConfigKeys.IKEY_PRODUCT_VARIANT, 95 keys.ConfigKeys.IKEY_BUILD_FLAVOR, 96 keys.ConfigKeys.IKEY_BUILD_ID, keys.ConfigKeys.IKEY_BRANCH, 97 keys.ConfigKeys.IKEY_BUILD_ALIAS, 98 keys.ConfigKeys.IKEY_API_LEVEL, keys.ConfigKeys.IKEY_SERIAL 99 ]: 100 if elem in device_spec: 101 setattr(dev_info, elem, str(device_spec[elem])) 102 # TODO: get abi information differently for multi-device support. 103 setattr(dev_info, keys.ConfigKeys.IKEY_ABI_NAME, 104 str(getattr(self, keys.ConfigKeys.IKEY_ABI_NAME))) 105 setattr(dev_info, keys.ConfigKeys.IKEY_ABI_BITNESS, 106 str(getattr(self, keys.ConfigKeys.IKEY_ABI_BITNESS))) 107 108 def SetTestResult(self, result=None): 109 """Set the current test case result to the provided result. 110 111 If None is provided as a result, the current test report will be cleared, which results 112 in a silent skip. 113 114 Requires the feature to be enabled; no-op otherwise. 115 116 Args: 117 result: ReportMsg.TestCaseResult, the result of the current test or None. 118 """ 119 if not self.enabled: 120 return 121 122 if not result: 123 self.report_msg.test_case.remove(self.current_test_report_msg) 124 self.current_test_report_msg = None 125 else: 126 self.current_test_report_msg.test_result = result 127 128 def AddTestReport(self, test_name): 129 """Creates a report for the specified test. 130 131 Requires the feature to be enabled; no-op otherwise. 132 133 Args: 134 test_name: String, the name of the test 135 """ 136 if not self.enabled: 137 return 138 self.current_test_report_msg = self.report_msg.test_case.add() 139 self.current_test_report_msg.name = test_name 140 self.current_test_report_msg.start_timestamp = feature_utils.GetTimestamp( 141 ) 142 143 def AddCoverageReport(self, 144 coverage_vec, 145 src_file_path, 146 git_project_name, 147 git_project_path, 148 revision, 149 covered_count, 150 line_count, 151 isGlobal=True): 152 """Adds a coverage report to the VtsReportMessage. 153 154 Processes the source information, git project information, and processed 155 coverage information and stores it into a CoverageReportMessage within the 156 report message. 157 158 Args: 159 coverage_vec: list, list of coverage counts (int) for each line 160 src_file_path: the path to the original source file 161 git_project_name: the name of the git project containing the source 162 git_project_path: the path from the root to the git project 163 revision: the commit hash identifying the source code that was used to 164 build a device image 165 covered_count: int, number of lines covered 166 line_count: int, total number of lines 167 isGlobal: boolean, True if the coverage data is for the entire test, False if only for 168 the current test case. 169 """ 170 if not self.enabled: 171 return 172 173 if isGlobal: 174 report = self.report_msg 175 else: 176 report = self.current_test_report_msg 177 178 coverage = report.coverage.add() 179 coverage.total_line_count = line_count 180 coverage.covered_line_count = covered_count 181 coverage.line_coverage_vector.extend(coverage_vec) 182 183 src_file_path = os.path.relpath(src_file_path, git_project_path) 184 coverage.file_path = src_file_path 185 coverage.revision = revision 186 coverage.project_name = git_project_name 187 188 def AddProfilingDataTimestamp( 189 self, 190 name, 191 start_timestamp, 192 end_timestamp, 193 x_axis_label="Latency (nano secs)", 194 y_axis_label="Frequency", 195 regression_mode=ReportMsg.VTS_REGRESSION_MODE_INCREASING): 196 """Adds the timestamp profiling data to the web DB. 197 198 Requires the feature to be enabled; no-op otherwise. 199 200 Args: 201 name: string, profiling point name. 202 start_timestamp: long, nanoseconds start time. 203 end_timestamp: long, nanoseconds end time. 204 x-axis_label: string, the x-axis label title for a graph plot. 205 y-axis_label: string, the y-axis label title for a graph plot. 206 regression_mode: specifies the direction of change which indicates 207 performance regression. 208 """ 209 if not self.enabled: 210 return 211 212 if not hasattr(self, _PROFILING_POINTS): 213 setattr(self, _PROFILING_POINTS, set()) 214 215 if name in getattr(self, _PROFILING_POINTS): 216 logging.error("profiling point %s is already active.", name) 217 return 218 219 getattr(self, _PROFILING_POINTS).add(name) 220 profiling_msg = self.report_msg.profiling.add() 221 profiling_msg.name = name 222 profiling_msg.type = ReportMsg.VTS_PROFILING_TYPE_TIMESTAMP 223 profiling_msg.regression_mode = regression_mode 224 profiling_msg.start_timestamp = start_timestamp 225 profiling_msg.end_timestamp = end_timestamp 226 profiling_msg.x_axis_label = x_axis_label 227 profiling_msg.y_axis_label = y_axis_label 228 229 def AddProfilingDataVector( 230 self, 231 name, 232 labels, 233 values, 234 data_type, 235 options=[], 236 x_axis_label="x-axis", 237 y_axis_label="y-axis", 238 regression_mode=ReportMsg.VTS_REGRESSION_MODE_INCREASING): 239 """Adds the vector profiling data in order to upload to the web DB. 240 241 Requires the feature to be enabled; no-op otherwise. 242 243 Args: 244 name: string, profiling point name. 245 labels: a list or set of labels. 246 values: a list or set of values where each value is an integer. 247 data_type: profiling data type. 248 options: a set of options. 249 x-axis_label: string, the x-axis label title for a graph plot. 250 y-axis_label: string, the y-axis label title for a graph plot. 251 regression_mode: specifies the direction of change which indicates 252 performance regression. 253 """ 254 if not self.enabled: 255 return 256 257 if not hasattr(self, _PROFILING_POINTS): 258 setattr(self, _PROFILING_POINTS, set()) 259 260 if name in getattr(self, _PROFILING_POINTS): 261 logging.error("profiling point %s is already active.", name) 262 return 263 264 getattr(self, _PROFILING_POINTS).add(name) 265 profiling_msg = self.report_msg.profiling.add() 266 profiling_msg.name = name 267 profiling_msg.type = data_type 268 profiling_msg.regression_mode = regression_mode 269 if labels: 270 profiling_msg.label.extend(labels) 271 profiling_msg.value.extend(values) 272 profiling_msg.x_axis_label = x_axis_label 273 profiling_msg.y_axis_label = y_axis_label 274 profiling_msg.options.extend(options) 275 276 def AddProfilingDataLabeledVector( 277 self, 278 name, 279 labels, 280 values, 281 options=[], 282 x_axis_label="x-axis", 283 y_axis_label="y-axis", 284 regression_mode=ReportMsg.VTS_REGRESSION_MODE_INCREASING): 285 """Adds the labeled vector profiling data in order to upload to the web DB. 286 287 Requires the feature to be enabled; no-op otherwise. 288 289 Args: 290 name: string, profiling point name. 291 labels: a list or set of labels. 292 values: a list or set of values where each value is an integer. 293 options: a set of options. 294 x-axis_label: string, the x-axis label title for a graph plot. 295 y-axis_label: string, the y-axis label title for a graph plot. 296 regression_mode: specifies the direction of change which indicates 297 performance regression. 298 """ 299 self.AddProfilingDataVector( 300 name, labels, values, ReportMsg.VTS_PROFILING_TYPE_LABELED_VECTOR, 301 options, x_axis_label, y_axis_label, regression_mode) 302 303 def AddProfilingDataUnlabeledVector( 304 self, 305 name, 306 values, 307 options=[], 308 x_axis_label="x-axis", 309 y_axis_label="y-axis", 310 regression_mode=ReportMsg.VTS_REGRESSION_MODE_INCREASING): 311 """Adds the unlabeled vector profiling data in order to upload to the web DB. 312 313 Requires the feature to be enabled; no-op otherwise. 314 315 Args: 316 name: string, profiling point name. 317 values: a list or set of values where each value is an integer. 318 options: a set of options. 319 x-axis_label: string, the x-axis label title for a graph plot. 320 y-axis_label: string, the y-axis label title for a graph plot. 321 regression_mode: specifies the direction of change which indicates 322 performance regression. 323 """ 324 self.AddProfilingDataVector( 325 name, None, values, ReportMsg.VTS_PROFILING_TYPE_UNLABELED_VECTOR, 326 options, x_axis_label, y_axis_label, regression_mode) 327 328 def AddSystraceUrl(self, url): 329 """Creates a systrace report message with a systrace URL. 330 331 Adds a systrace report to the current test case report and supplies the 332 url to the systrace report. 333 334 Requires the feature to be enabled; no-op otherwise. 335 336 Args: 337 url: String, the url of the systrace report. 338 """ 339 if not self.enabled: 340 return 341 systrace_msg = self.current_test_report_msg.systrace.add() 342 systrace_msg.url.append(url) 343 344 def AddLogUrls(self, urls): 345 """Creates a log message with log file URLs. 346 347 Adds a log message to the current test module report and supplies the 348 url to the log files. 349 350 Requires the feature to be enabled; no-op otherwise. 351 352 Args: 353 urls: list of string, the URLs of the logs. 354 """ 355 if not self.enabled or urls is None: 356 return 357 358 for url in urls: 359 log_msg = self.report_msg.log.add() 360 log_msg.url = url 361 log_msg.name = os.path.basename(url) 362 363 def Upload(self, requested, executed): 364 """Uploads the result to the web service. 365 366 Requires the feature to be enabled; no-op otherwise. 367 368 Args: 369 requested: list, A list of test case names requested to run 370 executed: list, A list of test case names that were executed 371 """ 372 if not self.enabled: 373 return 374 375 # Handle case when runner fails, tests aren't executed 376 if (executed and executed[-1].test_name == "setup_class"): 377 # Test failed during setup, all tests were not executed 378 start_index = 0 379 else: 380 # Runner was aborted. Remaining tests weren't executed 381 start_index = len(executed) 382 383 for test in requested[start_index:]: 384 msg = self.report_msg.test_case.add() 385 msg.name = test 386 msg.start_timestamp = feature_utils.GetTimestamp() 387 msg.end_timestamp = msg.start_timestamp 388 msg.test_result = ReportMsg.TEST_CASE_RESULT_FAIL 389 390 self.report_msg.end_timestamp = feature_utils.GetTimestamp() 391 392 build = getattr(self, keys.ConfigKeys.IKEY_BUILD) 393 if keys.ConfigKeys.IKEY_BUILD_ID in build: 394 build_id = str(build[keys.ConfigKeys.IKEY_BUILD_ID]) 395 self.report_msg.build_info.id = build_id 396 397 logging.info("_tearDownClass hook: start (username: %s)", 398 getpass.getuser()) 399 400 if len(self.report_msg.test_case) > 0: 401 post_msg = ReportMsg.DashboardPostMessage() 402 post_msg.test_report.extend([self.report_msg]) 403 404 # Post new data to the dashboard 405 self.rest_client.PostData(post_msg) 406 407 logging.info("_tearDownClass hook: status upload time stamp %s", 408 str(self.report_msg.start_timestamp)) 409 else: 410 logging.info("_tearDownClass hook: skip uploading (no test case)") 411