• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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