1# 2# Copyright (C) 2016 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 16import logging 17import os 18 19from google.protobuf import text_format 20from vts.proto import VtsProfilingMessage_pb2 as VtsProfilingMsg 21from vts.proto import VtsReportMessage_pb2 as ReportMsg 22from vts.runners.host import asserts 23from vts.runners.host import const 24from vts.runners.host import keys 25from vts.utils.python.common import cmd_utils 26from vts.utils.python.os import path_utils 27from vts.utils.python.web import feature_utils 28 29LOCAL_PROFILING_TRACE_PATH = "/tmp/vts-test-trace" 30TARGET_PROFILING_TRACE_PATH = "/data/local/tmp/" 31HAL_INSTRUMENTATION_LIB_PATH_32 = "/data/local/tmp/32/" 32HAL_INSTRUMENTATION_LIB_PATH_64 = "/data/local/tmp/64/" 33 34_PROFILING_DATA = "profiling_data" 35_HOST_PROFILING_DATA = "host_profiling_data" 36 37 38class VTSProfilingData(object): 39 """Class to store the VTS profiling data. 40 41 Attributes: 42 values: A dict that stores the profiling data. e.g. latencies of each api. 43 options: A set of strings where each string specifies an associated 44 option (which is the form of 'key=value'). 45 """ 46 47 def __init__(self): 48 self.values = {} 49 self.options = set() 50 51 52EVENT_TYPE_DICT = { 53 0: "SERVER_API_ENTRY", 54 1: "SERVER_API_EXIT", 55 2: "CLIENT_API_ENTRY", 56 3: "CLIENT_API_EXIT", 57 4: "SYNC_CALLBACK_ENTRY", 58 5: "SYNC_CALLBACK_EXIT", 59 6: "ASYNC_CALLBACK_ENTRY", 60 7: "ASYNC_CALLBACK_EXIT", 61 8: "PASSTHROUGH_ENTRY", 62 9: "PASSTHROUGH_EXIT", 63} 64 65 66class ProfilingFeature(feature_utils.Feature): 67 """Feature object for profiling functionality. 68 69 Attributes: 70 enabled: boolean, True if profiling is enabled, False otherwise 71 web: (optional) WebFeature, object storing web feature util for test run 72 """ 73 74 _TOGGLE_PARAM = keys.ConfigKeys.IKEY_ENABLE_PROFILING 75 _REQUIRED_PARAMS = [keys.ConfigKeys.IKEY_DATA_FILE_PATH] 76 _OPTIONAL_PARAMS = [ 77 keys.ConfigKeys.IKEY_PROFILING_TRACING_PATH, 78 keys.ConfigKeys.IKEY_TRACE_FILE_TOOL_NAME, 79 keys.ConfigKeys.IKEY_ABI_BITNESS, 80 ] 81 82 def __init__(self, user_params, web=None): 83 """Initializes the profiling feature. 84 85 Args: 86 user_params: A dictionary from parameter name (String) to parameter value. 87 web: (optional) WebFeature, object storing web feature util for test run 88 """ 89 self.ParseParameters(self._TOGGLE_PARAM, self._REQUIRED_PARAMS, 90 self._OPTIONAL_PARAMS, user_params) 91 self.web = web 92 logging.info("Profiling enabled: %s", self.enabled) 93 94 def _IsEventFromBinderizedHal(self, event_type): 95 """Returns True if the event type is from a binderized HAL.""" 96 if event_type in [8, 9]: 97 return False 98 return True 99 100 def GetTraceFiles(self, dut, host_profiling_trace_path, trace_file_tool): 101 """Pulls the trace file and save it under the profiling trace path. 102 103 Args: 104 dut: the testing device. 105 host_profiling_trace_path: directory that stores trace files on host. 106 trace_file_tool: tools that used to store the trace file. 107 108 Returns: 109 Name list of trace files that stored on host. 110 """ 111 if not os.path.exists(LOCAL_PROFILING_TRACE_PATH): 112 os.makedirs(LOCAL_PROFILING_TRACE_PATH) 113 114 if not host_profiling_trace_path: 115 host_profiling_trace_path = LOCAL_PROFILING_TRACE_PATH 116 117 dut.shell.InvokeTerminal("profiling_shell") 118 target_trace_file = path_utils.JoinTargetPath( 119 TARGET_PROFILING_TRACE_PATH, "*.vts.trace") 120 results = dut.shell.profiling_shell.Execute("ls " + target_trace_file) 121 asserts.assertTrue(results, "failed to find trace file") 122 stdout_lines = results[const.STDOUT][0].split("\n") 123 logging.info("stdout: %s", stdout_lines) 124 trace_files = [] 125 for line in stdout_lines: 126 if line: 127 temp_file_name = os.path.join(LOCAL_PROFILING_TRACE_PATH, 128 os.path.basename(line.strip())) 129 dut.adb.pull("%s %s" % (line, temp_file_name)) 130 trace_file_name = os.path.join(host_profiling_trace_path, 131 os.path.basename(line.strip())) 132 logging.info("Saving profiling traces: %s" % trace_file_name) 133 if temp_file_name != trace_file_name: 134 file_cmd = "" 135 if trace_file_tool: 136 file_cmd += trace_file_tool 137 file_cmd += " cp " + temp_file_name + " " + trace_file_name 138 results = cmd_utils.ExecuteShellCommand(file_cmd) 139 if results[const.EXIT_CODE][0] != 0: 140 logging.error(results[const.STDERR][0]) 141 logging.error("Fail to execute command: %s" % file_cmd) 142 trace_files.append(temp_file_name) 143 return trace_files 144 145 def EnableVTSProfiling(self, shell, hal_instrumentation_lib_path=None): 146 """ Enable profiling by setting the system property. 147 148 Args: 149 shell: shell to control the testing device. 150 hal_instrumentation_lib_path: directory that stores profiling libraries. 151 """ 152 if not hal_instrumentation_lib_path: 153 bitness = getattr(self, keys.ConfigKeys.IKEY_ABI_BITNESS, None) 154 if bitness == '64': 155 hal_instrumentation_lib_path = HAL_INSTRUMENTATION_LIB_PATH_64 156 elif bitness == '32': 157 hal_instrumentation_lib_path = HAL_INSTRUMENTATION_LIB_PATH_32 158 else: 159 logging.error('Unknown abi bitness "%s". Using 64bit hal ' 160 'instrumentation lib path.', bitness) 161 hal_instrumentation_lib_path = HAL_INSTRUMENTATION_LIB_PATH_64 162 163 # cleanup any existing traces. 164 shell.Execute("rm " + os.path.join(TARGET_PROFILING_TRACE_PATH, 165 "*.vts.trace")) 166 logging.info("enable VTS profiling.") 167 168 # give permission to write the trace file. 169 shell.Execute("chmod 777 " + TARGET_PROFILING_TRACE_PATH) 170 171 shell.Execute("setprop hal.instrumentation.lib.path " + 172 hal_instrumentation_lib_path) 173 shell.Execute("setprop hal.instrumentation.enable true") 174 175 def DisableVTSProfiling(self, shell): 176 """ Disable profiling by resetting the system property. 177 178 Args: 179 shell: shell to control the testing device. 180 """ 181 shell.Execute("setprop hal.instrumentation.lib.path \"\"") 182 shell.Execute("setprop hal.instrumentation.enable false") 183 184 def _ParseTraceData(self, trace_file): 185 """Parses the data stored in trace_file, calculates the avg/max/min 186 latency for each API. 187 188 Args: 189 trace_file: file that stores the trace data. 190 191 Returns: 192 VTSProfilingData which contain the list of API names and the avg/max/min 193 latency for each API. 194 """ 195 profiling_data = VTSProfilingData() 196 api_timestamps = {} 197 api_latencies = {} 198 199 data_file_path = getattr(self, keys.ConfigKeys.IKEY_DATA_FILE_PATH) 200 trace_processor_binary = os.path.join(data_file_path, 201 "host", "bin", "trace_processor") 202 trace_processor_lib = os.path.join(data_file_path, "host", "lib64") 203 trace_processor_cmd = [ 204 "chmod a+x %s" % trace_processor_binary, 205 "LD_LIBRARY_PATH=%s %s --profiling %s" % 206 (trace_processor_lib, trace_processor_binary, trace_file) 207 ] 208 209 results = cmd_utils.ExecuteShellCommand(trace_processor_cmd) 210 if any(results[cmd_utils.EXIT_CODE]): 211 logging.error("Fail to execute command: %s" % trace_processor_cmd) 212 return profiling_data 213 214 stdout_lines = results[const.STDOUT][1].split("\n") 215 first_line = True 216 for line in stdout_lines: 217 if not line: 218 continue 219 if first_line: 220 _, mode = line.split(":") 221 profiling_data.options.add("hidl_hal_mode=%s" % mode) 222 first_line = False 223 else: 224 api, latency = line.split(":") 225 if profiling_data.values.get(api): 226 profiling_data.values[api].append(long(latency)) 227 else: 228 profiling_data.values[api] = [long(latency)] 229 230 return profiling_data 231 232 def StartHostProfiling(self, name): 233 """Starts a profiling operation. 234 235 Args: 236 name: string, the name of a profiling point 237 238 Returns: 239 True if successful, False otherwise 240 """ 241 if not self.enabled: 242 return False 243 244 if not hasattr(self, _HOST_PROFILING_DATA): 245 setattr(self, _HOST_PROFILING_DATA, {}) 246 247 host_profiling_data = getattr(self, _HOST_PROFILING_DATA) 248 249 if name in host_profiling_data: 250 logging.error("profiling point %s is already active.", name) 251 return False 252 host_profiling_data[name] = feature_utils.GetTimestamp() 253 return True 254 255 def StopHostProfiling(self, name): 256 """Stops a profiling operation. 257 258 Args: 259 name: string, the name of a profiling point 260 """ 261 if not self.enabled: 262 return 263 264 if not hasattr(self, _HOST_PROFILING_DATA): 265 setattr(self, _HOST_PROFILING_DATA, {}) 266 267 host_profiling_data = getattr(self, _HOST_PROFILING_DATA) 268 269 if name not in host_profiling_data: 270 logging.error("profiling point %s is not active.", name) 271 return False 272 273 start_timestamp = host_profiling_data[name] 274 end_timestamp = feature_utils.GetTimestamp() 275 if self.web and self.web.enabled: 276 self.web.AddProfilingDataTimestamp(name, start_timestamp, 277 end_timestamp) 278 return True 279 280 def ProcessTraceDataForTestCase(self, dut): 281 """Pulls the generated trace file to the host, parses the trace file to 282 get the profiling data (e.g. latency of each API call) and stores these 283 data in _profiling_data. 284 285 Requires the feature to be enabled; no-op otherwise. 286 287 Args: 288 dut: the registered device. 289 """ 290 if not self.enabled: 291 return 292 293 if not hasattr(self, _PROFILING_DATA): 294 setattr(self, _PROFILING_DATA, []) 295 296 profiling_data = getattr(self, _PROFILING_DATA) 297 298 trace_files = self.GetTraceFiles( 299 dut, 300 getattr(self, keys.ConfigKeys.IKEY_PROFILING_TRACING_PATH, None), 301 getattr(self, keys.ConfigKeys.IKEY_TRACE_FILE_TOOL_NAME, None)) 302 for file in trace_files: 303 logging.info("parsing trace file: %s.", file) 304 data = self._ParseTraceData(file) 305 if data: 306 profiling_data.append(data) 307 308 def ProcessAndUploadTraceData(self): 309 """Process and upload profiling trace data. 310 311 Requires the feature to be enabled; no-op otherwise. 312 313 Merges the profiling data generated by each test case, calculates the 314 aggregated max/min/avg latency for each API and uploads these latency 315 metrics to webdb. 316 """ 317 if not self.enabled: 318 return 319 320 merged_profiling_data = VTSProfilingData() 321 for data in getattr(self, _PROFILING_DATA, []): 322 for item in data.options: 323 merged_profiling_data.options.add(item) 324 for api, latences in data.values.items(): 325 if merged_profiling_data.values.get(api): 326 merged_profiling_data.values[api].extend(latences) 327 else: 328 merged_profiling_data.values[api] = latences 329 for api, latencies in merged_profiling_data.values.items(): 330 if not self.web or not self.web.enabled: 331 continue 332 333 self.web.AddProfilingDataUnlabeledVector( 334 api, 335 latencies, 336 merged_profiling_data.options, 337 x_axis_label="API processing latency (nano secs)", 338 y_axis_label="Frequency") 339