From 2155a0cd95143d10ad49a14db11417ba86f36840 Mon Sep 17 00:00:00 2001 From: Kajetan Puchalski Date: Tue, 4 Jul 2023 17:28:40 +0100 Subject: [PATCH 1/2] target: Add is_running() Add the "is_running" function that can be used to check if a given process is running on the target device. It will return True if a process matching the name is found and Falsa otherwise. Signed-off-by: Kajetan Puchalski --- devlib/target.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/devlib/target.py b/devlib/target.py index 8624aab79..f4c5ebd78 100644 --- a/devlib/target.py +++ b/devlib/target.py @@ -54,7 +54,8 @@ from devlib.exception import (DevlibTransientError, TargetStableError, TargetNotRespondingError, TimeoutError, TargetTransientError, KernelConfigKeyError, - TargetError, HostError, TargetCalledProcessError) # pylint: disable=redefined-builtin + TargetError, HostError, TargetCalledProcessError, + TargetStableCalledProcessError) # pylint: disable=redefined-builtin from devlib.utils.ssh import SshConnection from devlib.utils.android import AdbConnection, AndroidProperties, LogcatMonitor, adb_command, adb_disconnect, INTENT_FLAGS from devlib.utils.misc import memoized, isiterable, convert_new_lines, groupby_value @@ -279,6 +280,12 @@ def shutils(self): self._setup_shutils() return self._shutils + def is_running(self, comm): + cmd_ps = f'''{self.busybox} ps -A -T -o stat,comm''' + cmd_awk = f'''{self.busybox} awk 'BEGIN{{found=0}} {{state=$1; $1=""; if ($state != "Z" && $0 == " {comm}") {{found=1}}}} END {{print found}}' ''' + result = self.execute(f"{cmd_ps} | {cmd_awk}") + return bool(int(result)) + @tls_property def _conn(self): try: From 3e4bc0bad5deb6db25a43031bddcd62995664f32 Mon Sep 17 00:00:00 2001 From: Kajetan Puchalski Date: Wed, 30 Aug 2023 14:41:49 +0100 Subject: [PATCH 2/2] collector: Add PerfettoCollector Add a Collector for accessing Google's Perfetto tracing infrastructure. The Collector takes a path to an on-device config file, starts tracing in the background using the perfetto binary and then stops by killing the tracing process. Signed-off-by: Kajetan Puchalski --- devlib/__init__.py | 1 + devlib/collector/perfetto.py | 120 +++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 devlib/collector/perfetto.py diff --git a/devlib/__init__.py b/devlib/__init__.py index 5e3bee25a..fe9965d4b 100644 --- a/devlib/__init__.py +++ b/devlib/__init__.py @@ -46,6 +46,7 @@ from devlib.derived.fps import DerivedGfxInfoStats, DerivedSurfaceFlingerStats from devlib.collector.ftrace import FtraceCollector +from devlib.collector.perfetto import PerfettoCollector from devlib.collector.perf import PerfCollector from devlib.collector.serial_trace import SerialTraceCollector from devlib.collector.dmesg import DmesgCollector diff --git a/devlib/collector/perfetto.py b/devlib/collector/perfetto.py new file mode 100644 index 000000000..7383d093d --- /dev/null +++ b/devlib/collector/perfetto.py @@ -0,0 +1,120 @@ +# Copyright 2023 ARM Limited +# +# 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. +# + +import os +import subprocess +from shlex import quote + +from devlib.host import PACKAGE_BIN_DIRECTORY +from devlib.collector import (CollectorBase, CollectorOutput, + CollectorOutputEntry) +from devlib.exception import TargetStableError, HostError + +OUTPUT_PERFETTO_TRACE = 'devlib-trace.perfetto-trace' + + +class PerfettoCollector(CollectorBase): + """ + Perfetto is a production-grade open-source stack for performance instrumentation + and trace analysis developed by Google. It offers services and libraries for + recording system-level and app-level traces, native + java heap profiling, + a library for analyzing traces using SQL and a web-based UI to visualize and + explore multi-GB traces. + + This collector takes a path to a perfetto config file saved on disk and passes + it directly to the tool. + + On Android platfroms Perfetto is included in the framework starting with Android 9. + On Android 8 and below, follow the Linux instructions below to build and include + the standalone tracebox binary. + + On Linux platforms, either traced (Perfetto tracing daemon) needs to be running + in the background or the tracebox binary needs to be built from source and placed + in the Package Bin directory. The build instructions can be found here: + + It is also possible to force using the prebuilt tracebox binary on platforms which + already have traced running using the force_tracebox collector parameter. + + https://perfetto.dev/docs/contributing/build-instructions + + After building the 'tracebox' binary should be copied to devlib/bin//. + + For more information consult the official documentation: + https://perfetto.dev/docs/ + """ + + def __init__(self, target, config=None, force_tracebox=False): + super().__init__(target) + self.bg_cmd = None + self.config = config + self.target_binary = 'perfetto' + target_output_path = self.target.working_directory + + install_tracebox = force_tracebox or (target.os in ['linux', 'android'] and not target.is_running('traced')) + + # Install Perfetto through tracebox + if install_tracebox: + self.target_binary = 'tracebox' + if not self.target.get_installed(self.target_binary): + host_executable = os.path.join(PACKAGE_BIN_DIRECTORY, + self.target.abi, self.target_binary) + if not os.path.exists(host_executable): + raise HostError("{} not found on the host".format(self.target_binary)) + self.target.install(host_executable) + # Use Android's built-in Perfetto + elif target.os == 'android': + os_version = target.os_version['release'] + if int(os_version) >= 9: + # Android requires built-in Perfetto to write to this directory + target_output_path = '/data/misc/perfetto-traces' + # Android 9 and 10 require traced to be enabled manually + if int(os_version) <= 10: + target.execute('setprop persist.traced.enable 1') + + self.target_output_file = target.path.join(target_output_path, OUTPUT_PERFETTO_TRACE) + + def start(self): + cmd = "cat {} | {} --txt -c - -o {}".format( + quote(self.config), quote(self.target_binary), quote(self.target_output_file) + ) + # start tracing + if self.bg_cmd is None: + self.bg_cmd = self.target.background(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + else: + raise TargetStableError('Perfetto collector is not re-entrant') + + def stop(self): + # stop tracing + self.bg_cmd.cancel() + self.bg_cmd = None + + def set_output(self, output_path): + if os.path.isdir(output_path): + output_path = os.path.join(output_path, os.path.basename(self.target_output_file)) + self.output_path = output_path + + def get_data(self): + if self.output_path is None: + raise RuntimeError("Output path was not set.") + if not self.target.file_exists(self.target_output_file): + raise RuntimeError("Output file not found on the device") + self.target.pull(self.target_output_file, self.output_path) + output = CollectorOutput() + if not os.path.isfile(self.output_path): + self.logger.warning('Perfetto trace not pulled from device.') + else: + output.append(CollectorOutputEntry(self.output_path, 'file')) + return output +