From d81b72a91be1d98a9efad62cf7e776a5a0d8e817 Mon Sep 17 00:00:00 2001 From: Valentin Schneider Date: Thu, 21 Jun 2018 19:10:42 +0100 Subject: [PATCH] trace: Add a Systrace TraceCollector --- devlib/trace/systrace.py | 158 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 devlib/trace/systrace.py diff --git a/devlib/trace/systrace.py b/devlib/trace/systrace.py new file mode 100644 index 0000000..7c72549 --- /dev/null +++ b/devlib/trace/systrace.py @@ -0,0 +1,158 @@ +# Copyright 2018 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 shutil import copyfile +from tempfile import NamedTemporaryFile + +from devlib.exception import TargetError, HostError +from devlib.trace import TraceCollector +from devlib.utils.android import platform_tools +from devlib.utils.misc import memoized + + +DEFAULT_CATEGORIES = [ + 'gfx', + 'view', + 'sched', + 'freq', + 'idle' +] + +class SystraceCollector(TraceCollector): + """ + A trace collector based on Systrace + + For more details, see https://developer.android.com/studio/command-line/systrace + + :param target: Devlib target + :type target: AndroidTarget + + :param outdir: Working directory to use on the host + :type outdir: str + + :param categories: Systrace categories to trace. See `available_categories` + :type categories: list(str) + + :param buffer_size: Buffer size in kb + :type buffer_size: int + + :param strict: Raise an exception if any of the requested categories + are not available + :type strict: bool + """ + + @property + @memoized + def available_categories(self): + lines = subprocess.check_output([self.systrace_binary, '-l']).splitlines() + + categories = [] + for line in lines: + categories.append(line.split()[0]) + + return categories + + def __init__(self, target, + categories=None, + buffer_size=None, + strict=False): + + super(SystraceCollector, self).__init__(target) + + self.categories = categories or DEFAULT_CATEGORIES + self.buffer_size = buffer_size + + self._systrace_process = None + self._tmpfile = None + + # Try to find a systrace binary + self.systrace_binary = None + + systrace_binary_path = os.path.join(platform_tools, 'systrace', 'systrace.py') + if not os.path.isfile(systrace_binary_path): + raise HostError('Could not find any systrace binary under {}'.format(platform_tools)) + + self.systrace_binary = systrace_binary_path + + # Filter the requested categories + for category in self.categories: + if category not in self.available_categories: + message = 'Category [{}] not available for tracing'.format(category) + if strict: + raise TargetError(message) + self.logger.warning(message) + + self.categories = list(set(self.categories) & set(self.available_categories)) + if not self.categories: + raise TargetError('None of the requested categories are available') + + def __del__(self): + self.reset() + + def _build_cmd(self): + self._tmpfile = NamedTemporaryFile() + + self.systrace_cmd = '{} -o {} -e {}'.format( + self.systrace_binary, + self._tmpfile.name, + self.target.adb_name + ) + + if self.buffer_size: + self.systrace_cmd += ' -b {}'.format(self.buffer_size) + + self.systrace_cmd += ' {}'.format(' '.join(self.categories)) + + def reset(self): + if self._systrace_process: + self.stop() + + if self._tmpfile: + self._tmpfile.close() + self._tmpfile = None + + def start(self): + if self._systrace_process: + raise RuntimeError("Tracing is already underway, call stop() first") + + self.reset() + + self._build_cmd() + + self._systrace_process = subprocess.Popen( + self.systrace_cmd, + stdin=subprocess.PIPE, + shell=True + ) + + def stop(self): + if not self._systrace_process: + raise RuntimeError("No tracing to stop, call start() first") + + # Systrace expects to stop + self._systrace_process.communicate('\n') + self._systrace_process = None + + def get_trace(self, outfile): + if self._systrace_process: + raise RuntimeError("Tracing is underway, call stop() first") + + if not self._tmpfile: + raise RuntimeError("No tracing data available") + + copyfile(self._tmpfile.name, outfile)