diff --git a/external/devlib/devlib/bin/ppc64le/busybox b/external/devlib/devlib/bin/ppc64le/busybox new file mode 100755 index 0000000000000000000000000000000000000000..43fd0d524012bbdfacb07929ab0a134be34d3968 Binary files /dev/null and b/external/devlib/devlib/bin/ppc64le/busybox differ diff --git a/external/devlib/devlib/bin/ppc64le/trace-cmd b/external/devlib/devlib/bin/ppc64le/trace-cmd new file mode 100755 index 0000000000000000000000000000000000000000..b6eeaff6635128e2f80f2d1df396e4daa2bdd089 Binary files /dev/null and b/external/devlib/devlib/bin/ppc64le/trace-cmd differ diff --git a/external/devlib/devlib/host.py b/external/devlib/devlib/host.py index 70461fac09aa0d79ca70c5fb03ea477e57257032..48e785f2bc00e88b5dafac91d8a3d1630894a3be 100644 --- a/external/devlib/devlib/host.py +++ b/external/devlib/devlib/host.py @@ -20,6 +20,7 @@ import subprocess import logging from distutils.dir_util import copy_tree from getpass import getpass +from pipes import quote from devlib.exception import TargetTransientError, TargetStableError from devlib.utils.misc import check_output @@ -70,7 +71,7 @@ class LocalConnection(object): if self.unrooted: raise TargetStableError('unrooted') password = self._get_password() - command = 'echo \'{}\' | sudo -S '.format(password) + command + command = 'echo {} | sudo -S '.format(quote(password)) + command ignore = None if check_exit_code else 'all' try: return check_output(command, shell=True, timeout=timeout, ignore=ignore)[0] @@ -87,7 +88,7 @@ class LocalConnection(object): if self.unrooted: raise TargetStableError('unrooted') password = self._get_password() - command = 'echo \'{}\' | sudo -S '.format(password) + command + command = 'echo {} | sudo -S '.format(quote(password)) + command return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True) def close(self): diff --git a/external/devlib/devlib/instrument/__init__.py b/external/devlib/devlib/instrument/__init__.py index 5214c3b1601cf07fef97d0d00e9b844649df94fb..9eaef7bda102948013ef195af90b14c41598ad47 100644 --- a/external/devlib/devlib/instrument/__init__.py +++ b/external/devlib/devlib/instrument/__init__.py @@ -320,7 +320,7 @@ class Instrument(object): wanted = lambda ch: ((kinds is None or ch.kind in kinds) and (sites is None or ch.site in sites)) - self.active_channels = filter(wanted, self.channels.values()) + self.active_channels = list(filter(wanted, self.channels.values())) # instantaneous diff --git a/external/devlib/devlib/instrument/acmecape.py b/external/devlib/devlib/instrument/acmecape.py index 16a3e1bd7ef66b7bff11ae980d055bb0da44d0ef..42728e69e52c890a15c8a11099a0a5fccc53675e 100644 --- a/external/devlib/devlib/instrument/acmecape.py +++ b/external/devlib/devlib/instrument/acmecape.py @@ -19,9 +19,11 @@ import os import sys import time import tempfile +import shlex from fcntl import fcntl, F_GETFL, F_SETFL from string import Template from subprocess import Popen, PIPE, STDOUT +from pipes import quote from devlib import Instrument, CONTINUOUS, MeasurementsCsv from devlib.exception import HostError @@ -89,11 +91,12 @@ class AcmeCapeInstrument(Instrument): iio_device=self.iio_device, outfile=self.raw_data_file ) + params = {k: quote(v) for k, v in params.items()} self.command = IIOCAP_CMD_TEMPLATE.substitute(**params) self.logger.debug('ACME cape command: {}'.format(self.command)) def start(self): - self.process = Popen(self.command.split(), stdout=PIPE, stderr=STDOUT) + self.process = Popen(shlex.split(self.command), stdout=PIPE, stderr=STDOUT) def stop(self): self.process.terminate() diff --git a/external/devlib/devlib/instrument/arm_energy_probe.py b/external/devlib/devlib/instrument/arm_energy_probe.py index 05ca6d62bae1c54d0c900324c87572e9576841a5..4db97d8c4b817a9d2523e01eca19eedd981972e7 100644 --- a/external/devlib/devlib/instrument/arm_energy_probe.py +++ b/external/devlib/devlib/instrument/arm_energy_probe.py @@ -34,6 +34,7 @@ from __future__ import division import os import subprocess import signal +from pipes import quote import tempfile import shutil @@ -97,7 +98,7 @@ class ArmEnergyProbeInstrument(Instrument): self.output_file_figure = os.path.join(self.output_directory, 'summary.txt') self.output_file_error = os.path.join(self.output_directory, 'error.log') self.output_fd_error = open(self.output_file_error, 'w') - self.command = 'arm-probe --config {} > {}'.format(self.config_file, self.output_file_raw) + self.command = 'arm-probe --config {} > {}'.format(quote(self.config_file), quote(self.output_file_raw)) def start(self): self.logger.debug(self.command) diff --git a/external/devlib/devlib/instrument/energy_probe.py b/external/devlib/devlib/instrument/energy_probe.py index 16aa7e51b4289e4db81e6e7dadf1dee6d8973bc3..4175a5d7e5623fa382a502b6dd99882381c1eeb0 100644 --- a/external/devlib/devlib/instrument/energy_probe.py +++ b/external/devlib/devlib/instrument/energy_probe.py @@ -19,6 +19,7 @@ import tempfile import struct import subprocess import sys +from pipes import quote from devlib.instrument import Instrument, CONTINUOUS, MeasurementsCsv from devlib.exception import HostError @@ -65,7 +66,10 @@ class EnergyProbeInstrument(Instrument): parts = ['-r {}:{} '.format(i, int(1000 * rval)) for i, rval in enumerate(self.resistor_values)] rstring = ''.join(parts) - self.command = '{} -d {} -l {} {}'.format(self.caiman, self.device_entry, rstring, self.raw_output_directory) + self.command = '{} -d {} -l {} {}'.format( + quote(self.caiman), quote(self.device_entry), + rstring, quote(self.raw_output_directory) + ) self.raw_data_file = None def start(self): diff --git a/external/devlib/devlib/module/sched.py b/external/devlib/devlib/module/sched.py index 9ee7cfa97cacaae928122294ebda54492287ba1c..c391c019582d4f86621217945f990674472cacff 100644 --- a/external/devlib/devlib/module/sched.py +++ b/external/devlib/devlib/module/sched.py @@ -15,6 +15,7 @@ import logging import re +from enum import Enum from past.builtins import basestring @@ -138,52 +139,74 @@ class SchedProcFSNode(object): self._dyn_attrs[key] = self._build_node(key, nodes[key]) -class SchedDomain(SchedProcFSNode): +class DocInt(int): + + # See https://stackoverflow.com/a/50473952/5096023 + def __new__(cls, value, doc): + new = super(DocInt, cls).__new__(cls, value) + new.__doc__ = doc + return new + + +class SchedDomainFlag(DocInt, Enum): """ - Represents a sched domain as seen through procfs + Represents a sched domain flag """ # pylint: disable=bad-whitespace # Domain flags obtained from include/linux/sched/topology.h on v4.17 # https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux/+/v4.17/include/linux/sched/topology.h#20 - SD_LOAD_BALANCE = 0x0001 # Do load balancing on this domain. - SD_BALANCE_NEWIDLE = 0x0002 # Balance when about to become idle - SD_BALANCE_EXEC = 0x0004 # Balance on exec - SD_BALANCE_FORK = 0x0008 # Balance on fork, clone - SD_BALANCE_WAKE = 0x0010 # Balance on wakeup - SD_WAKE_AFFINE = 0x0020 # Wake task to waking CPU - SD_ASYM_CPUCAPACITY = 0x0040 # Groups have different max cpu capacities - SD_SHARE_CPUCAPACITY = 0x0080 # Domain members share cpu capacity - SD_SHARE_POWERDOMAIN = 0x0100 # Domain members share power domain - SD_SHARE_PKG_RESOURCES = 0x0200 # Domain members share cpu pkg resources - SD_SERIALIZE = 0x0400 # Only a single load balancing instance - SD_ASYM_PACKING = 0x0800 # Place busy groups earlier in the domain - SD_PREFER_SIBLING = 0x1000 # Prefer to place tasks in a sibling domain - SD_OVERLAP = 0x2000 # sched_domains of this level overlap - SD_NUMA = 0x4000 # cross-node balancing + SD_LOAD_BALANCE = 0x0001, "Do load balancing on this domain" + SD_BALANCE_NEWIDLE = 0x0002, "Balance when about to become idle" + SD_BALANCE_EXEC = 0x0004, "Balance on exec" + SD_BALANCE_FORK = 0x0008, "Balance on fork, clone" + SD_BALANCE_WAKE = 0x0010, "Balance on wakeup" + SD_WAKE_AFFINE = 0x0020, "Wake task to waking CPU" + SD_ASYM_CPUCAPACITY = 0x0040, "Groups have different max cpu capacities" + SD_SHARE_CPUCAPACITY = 0x0080, "Domain members share cpu capacity" + SD_SHARE_POWERDOMAIN = 0x0100, "Domain members share power domain" + SD_SHARE_PKG_RESOURCES = 0x0200, "Domain members share cpu pkg resources" + SD_SERIALIZE = 0x0400, "Only a single load balancing instance" + SD_ASYM_PACKING = 0x0800, "Place busy groups earlier in the domain" + SD_PREFER_SIBLING = 0x1000, "Prefer to place tasks in a sibling domain" + SD_OVERLAP = 0x2000, "Sched_domains of this level overlap" + SD_NUMA = 0x4000, "Cross-node balancing" # Only defined in Android # https://android.googlesource.com/kernel/common/+/android-4.14/include/linux/sched/topology.h#29 - SD_SHARE_CAP_STATES = 0x8000 # Domain members share capacity state + SD_SHARE_CAP_STATES = 0x8000, "(Android only) Domain members share capacity state" - # Checked to be valid from v4.4 - SD_FLAGS_REF_PARTS = (4, 4, 0) - - @staticmethod - def check_version(target, logger): + @classmethod + def check_version(cls, target, logger): """ Check the target and see if its kernel version matches our view of the world """ parts = target.kernel_version.parts - if parts < SchedDomain.SD_FLAGS_REF_PARTS: + # Checked to be valid from v4.4 + # Not saved as a class attribute else it'll be converted to an enum + ref_parts = (4, 4, 0) + if parts < ref_parts: logger.warn( "Sched domain flags are defined for kernels v{} and up, " - "but target is running v{}".format(SchedDomain.SD_FLAGS_REF_PARTS, parts) + "but target is running v{}".format(ref_parts, parts) ) - def has_flags(self, flags): - """ - :returns: Whether 'flags' are set on this sched domain - """ - return self.flags & flags == flags + + def __str__(self): + return self.name + + +class SchedDomain(SchedProcFSNode): + """ + Represents a sched domain as seen through procfs + """ + def __init__(self, nodes): + super(SchedDomain, self).__init__(nodes) + + obj_flags = set() + for flag in list(SchedDomainFlag): + if self.flags & flag.value == flag.value: + obj_flags.add(flag) + + self.flags = obj_flags class SchedProcFSData(SchedProcFSNode): @@ -226,7 +249,7 @@ class SchedModule(Module): @staticmethod def probe(target): logger = logging.getLogger(SchedModule.name) - SchedDomain.check_version(target, logger) + SchedDomainFlag.check_version(target, logger) return SchedProcFSData.available(target) diff --git a/external/devlib/devlib/platform/gem5.py b/external/devlib/devlib/platform/gem5.py index 817699a64daecea9022aba6629739cd1f8c6186f..9fa82547f84cdc61fb2b46c631eab7122be489e6 100644 --- a/external/devlib/devlib/platform/gem5.py +++ b/external/devlib/devlib/platform/gem5.py @@ -18,6 +18,8 @@ import subprocess import shutil import time import types +import shlex +from pipes import quote from devlib.exception import TargetStableError from devlib.host import PACKAGE_BIN_DIRECTORY @@ -129,11 +131,11 @@ class Gem5SimulationPlatform(Platform): self.logger.info("Starting the gem5 simulator") command_line = "{} --outdir={} {} {}".format(self.gem5args_binary, - self.gem5_out_dir, + quote(self.gem5_out_dir), self.gem5args_args, self.gem5args_virtio) self.logger.debug("gem5 command line: {}".format(command_line)) - self.gem5 = subprocess.Popen(command_line.split(), + self.gem5 = subprocess.Popen(shlex.split(command_line), stdout=self.stdout_file, stderr=self.stderr_file) diff --git a/external/devlib/devlib/target.py b/external/devlib/devlib/target.py index 3cddce7adc1246be6b2efa150a2173237d06b0d6..475c166bfe340eb91780290b6c7a668ae03387b7 100644 --- a/external/devlib/devlib/target.py +++ b/external/devlib/devlib/target.py @@ -26,6 +26,7 @@ import threading import xml.dom.minidom import copy from collections import namedtuple, defaultdict +from pipes import quote from devlib.host import LocalConnection, PACKAGE_BIN_DIRECTORY from devlib.module import get_module @@ -36,7 +37,7 @@ from devlib.exception import (DevlibTransientError, TargetStableError, 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 -from devlib.utils.misc import commonprefix, escape_double_quotes, merge_lists +from devlib.utils.misc import commonprefix, merge_lists from devlib.utils.misc import ABI_MAP, get_cpu_name, ranges_to_list from devlib.utils.types import integer, boolean, bitmask, identifier, caseless_string, bytes_regex @@ -45,6 +46,7 @@ FSTAB_ENTRY_REGEX = re.compile(r'(\S+) on (.+) type (\S+) \((\S+)\)') ANDROID_SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn|Display Power: state)=([0-9]+|true|false|ON|OFF)', re.IGNORECASE) ANDROID_SCREEN_RESOLUTION_REGEX = re.compile(r'cur=(?P\d+)x(?P\d+)') +ANDROID_SCREEN_ROTATION_REGEX = re.compile(r'orientation=(?P[0-3])') DEFAULT_SHELL_PROMPT = re.compile(r'^.*(shell|root|juno)@?.*:[/~]\S* *[#$] ', re.MULTILINE) KVERSION_REGEX = re.compile( @@ -117,7 +119,7 @@ class Target(object): @property @memoized def kernel_version(self): - return KernelVersion(self.execute('{} uname -r -v'.format(self.busybox)).strip()) + return KernelVersion(self.execute('{} uname -r -v'.format(quote(self.busybox))).strip()) @property def os_version(self): # pylint: disable=no-self-use @@ -155,7 +157,7 @@ class Target(object): except TargetStableError: for path in ['/boot/config', '/boot/config-$(uname -r)']: try: - return KernelConfig(self.execute('cat {}'.format(path))) + return KernelConfig(self.execute('cat {}'.format(quote(path)))) except TargetStableError: pass return KernelConfig('') @@ -252,8 +254,8 @@ class Target(object): if check_boot_completed: self.wait_boot_complete(timeout) self._resolve_paths() - self.execute('mkdir -p {}'.format(self.working_directory)) - self.execute('mkdir -p {}'.format(self.executables_directory)) + self.execute('mkdir -p {}'.format(quote(self.working_directory))) + self.execute('mkdir -p {}'.format(quote(self.executables_directory))) self.busybox = self.install(os.path.join(PACKAGE_BIN_DIRECTORY, self.abi, 'busybox')) self.platform.update_from_target(self) self._update_modules('connected') @@ -285,7 +287,7 @@ class Target(object): # Initialize modules which requires Buxybox (e.g. shutil dependent tasks) self._update_modules('setup') - self.execute('mkdir -p {}'.format(self._file_transfer_cache)) + self.execute('mkdir -p {}'.format(quote(self._file_transfer_cache))) def reboot(self, hard=False, connect=True, timeout=180): if hard: @@ -319,20 +321,20 @@ class Target(object): self.conn.push(source, dest, timeout=timeout) else: device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep)) - self.execute("mkdir -p '{}'".format(self.path.dirname(device_tempfile))) + self.execute("mkdir -p {}".format(quote(self.path.dirname(device_tempfile)))) self.conn.push(source, device_tempfile, timeout=timeout) - self.execute("cp '{}' '{}'".format(device_tempfile, dest), as_root=True) + self.execute("cp {} {}".format(quote(device_tempfile), quote(dest)), as_root=True) def pull(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ if not as_root: self.conn.pull(source, dest, timeout=timeout) else: device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep)) - self.execute("mkdir -p '{}'".format(self.path.dirname(device_tempfile))) - self.execute("cp -r '{}' '{}'".format(source, device_tempfile), as_root=True) - self.execute("chmod 0644 '{}'".format(device_tempfile), as_root=True) + self.execute("mkdir -p {}".format(quote(self.path.dirname(device_tempfile)))) + self.execute("cp -r {} {}".format(quote(source), quote(device_tempfile)), as_root=True) + self.execute("chmod 0644 {}".format(quote(device_tempfile)), as_root=True) self.conn.pull(device_tempfile, dest, timeout=timeout) - self.execute("rm -r '{}'".format(device_tempfile), as_root=True) + self.execute("rm -r {}".format(quote(device_tempfile)), as_root=True) def get_directory(self, source_dir, dest, as_root=False): """ Pull a directory from the device, after compressing dir """ @@ -349,11 +351,12 @@ class Target(object): tar_file_name = self.path.join(self._file_transfer_cache, tar_file_name) # Does the folder exist? - self.execute('ls -la {}'.format(source_dir), as_root=as_root) + self.execute('ls -la {}'.format(quote(source_dir)), as_root=as_root) # Try compressing the folder try: - self.execute('{} tar -cvf {} {}'.format(self.busybox, tar_file_name, - source_dir), as_root=as_root) + self.execute('{} tar -cvf {} {}'.format( + quote(self.busybox), quote(tar_file_name), quote(source_dir) + ), as_root=as_root) except TargetStableError: self.logger.debug('Failed to run tar command on target! ' \ 'Not pulling directory {}'.format(source_dir)) @@ -406,9 +409,9 @@ class Target(object): command = '{} {}'.format(command, args) if on_cpus: on_cpus = bitmask(on_cpus) - command = '{} taskset 0x{:x} {}'.format(self.busybox, on_cpus, command) + command = '{} taskset 0x{:x} {}'.format(quote(self.busybox), on_cpus, command) if in_directory: - command = 'cd {} && {}'.format(in_directory, command) + command = 'cd {} && {}'.format(quote(in_directory), command) if redirect_stderr: command = '{} 2>&1'.format(command) return self.execute(command, as_root=as_root, timeout=timeout) @@ -440,9 +443,9 @@ class Target(object): command = '{} {}'.format(command, args) if on_cpus: on_cpus = bitmask(on_cpus) - command = '{} taskset 0x{:x} {}'.format(self.busybox, on_cpus, command) + command = '{} taskset 0x{:x} {}'.format(quote(self.busybox), on_cpus, command) if in_directory: - command = 'cd {} && {}'.format(in_directory, command) + command = 'cd {} && {}'.format(quote(in_directory), command) return self.background(command, as_root=as_root) def kick_off(self, command, as_root=False): @@ -451,7 +454,7 @@ class Target(object): # sysfs interaction def read_value(self, path, kind=None): - output = self.execute('cat \'{}\''.format(path), as_root=self.needs_su).strip() # pylint: disable=E1103 + output = self.execute('cat {}'.format(quote(path)), as_root=self.needs_su).strip() # pylint: disable=E1103 if kind: return kind(output) else: @@ -465,7 +468,7 @@ class Target(object): def write_value(self, path, value, verify=True): value = str(value) - self.execute('echo {} > \'{}\''.format(value, path), check_exit_code=False, as_root=True) + self.execute('echo {} > {}'.format(quote(value), quote(path)), check_exit_code=False, as_root=True) if verify: output = self.read_value(path) if not output == value: @@ -511,12 +514,12 @@ class Target(object): # files def file_exists(self, filepath): - command = 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi' - output = self.execute(command.format(filepath), as_root=self.is_rooted) + command = 'if [ -e {} ]; then echo 1; else echo 0; fi' + output = self.execute(command.format(quote(filepath)), as_root=self.is_rooted) return boolean(output.strip()) def directory_exists(self, filepath): - output = self.execute('if [ -d \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath)) + output = self.execute('if [ -d {} ]; then echo 1; else echo 0; fi'.format(quote(filepath))) # output from ssh my contain part of the expression in the buffer, # split out everything except the last word. return boolean(output.split()[-1]) # pylint: disable=maybe-no-member @@ -553,7 +556,7 @@ class Target(object): raise IOError('No usable temporary filename found') def remove(self, path, as_root=False): - self.execute('rm -rf "{}"'.format(escape_double_quotes(path)), as_root=as_root) + self.execute('rm -rf {}'.format(quote(path)), as_root=as_root) # misc def core_cpus(self, core): @@ -641,7 +644,7 @@ class Target(object): def insmod(self, path): target_path = self.get_workpath(os.path.basename(path)) self.push(path, target_path) - self.execute('insmod {}'.format(target_path), as_root=True) + self.execute('insmod {}'.format(quote(target_path)), as_root=True) def extract(self, path, dest=None): @@ -682,7 +685,7 @@ class Target(object): self.execute('sleep {}'.format(duration), timeout=timeout) def read_tree_values_flat(self, path, depth=1, check_exit_code=True): - command = 'read_tree_values {} {}'.format(path, depth) + command = 'read_tree_values {} {}'.format(quote(path), depth) output = self._execute_util(command, as_root=self.is_rooted, check_exit_code=check_exit_code) @@ -730,17 +733,17 @@ class Target(object): extracted = dest else: extracted = self.path.dirname(path) - cmdtext = cmd.format(self.busybox, path, extracted) + cmdtext = cmd.format(quote(self.busybox), quote(path), quote(extracted)) self.execute(cmdtext) return extracted def _extract_file(self, path, cmd, dest=None): cmd = '{} ' + cmd # busybox - cmdtext = cmd.format(self.busybox, path) + cmdtext = cmd.format(quote(self.busybox), quote(path)) self.execute(cmdtext) extracted = self.path.splitext(path)[0] if dest: - self.execute('mv -f {} {}'.format(extracted, dest)) + self.execute('mv -f {} {}'.format(quote(extracted), quote(dest))) if dest.endswith('/'): extracted = self.path.join(dest, self.path.basename(extracted)) else: @@ -784,7 +787,7 @@ class Target(object): # It would be nice to use busybox for this, but that means we'd need # root (ping is usually setuid so it can open raw sockets to send ICMP) command = 'ping -q -c 1 -w {} {} 2>&1'.format(timeout_s, - GOOGLE_DNS_SERVER_ADDRESS) + quote(GOOGLE_DNS_SERVER_ADDRESS)) # We'll use our own retrying mechanism (rather than just using ping's -c # to send multiple packets) so that we don't slow things down in the @@ -889,13 +892,13 @@ class LinuxTarget(Target): pass def kick_off(self, command, as_root=False): - command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command)) + command = 'sh -c {} 1>/dev/null 2>/dev/null &'.format(quote(command)) return self.conn.execute(command, as_root=as_root) def get_pids_of(self, process_name): """Returns a list of PIDs of all processes with the specified name.""" # result should be a column of PIDs with the first row as "PID" header - result = self.execute('ps -C {} -o pid'.format(process_name), # NOQA + result = self.execute('ps -C {} -o pid'.format(quote(process_name)), # NOQA check_exit_code=False).strip().split() if len(result) >= 2: # at least one row besides the header return list(map(int, result[1:])) @@ -923,14 +926,14 @@ class LinuxTarget(Target): return filtered_result def list_directory(self, path, as_root=False): - contents = self.execute('ls -1 "{}"'.format(escape_double_quotes(path)), as_root=as_root) + contents = self.execute('ls -1 {}'.format(quote(path)), as_root=as_root) return [x.strip() for x in contents.split('\n') if x.strip()] def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221 destpath = self.path.join(self.executables_directory, with_name and with_name or self.path.basename(filepath)) self.push(filepath, destpath) - self.execute('chmod a+x {}'.format(destpath), timeout=timeout) + self.execute('chmod a+x {}'.format(quote(destpath)), timeout=timeout) self._installed_binaries[self.path.basename(destpath)] = destpath return destpath @@ -946,7 +949,7 @@ class LinuxTarget(Target): tmpfile = self.tempfile() cmd = 'DISPLAY=:0.0 scrot {} && {} date -u -Iseconds' - ts = self.execute(cmd.format(tmpfile, self.busybox)).strip() + ts = self.execute(cmd.format(quote(tmpfile), quote(self.busybox))).strip() filepath = filepath.format(ts=ts) self.pull(tmpfile, filepath) self.remove(tmpfile) @@ -1124,7 +1127,7 @@ class AndroidTarget(Target): if as_root is None: as_root = self.needs_su try: - command = 'cd {} && {} nohup {} &'.format(self.working_directory, self.busybox, command) + command = 'cd {} && {} nohup {} &'.format(quote(self.working_directory), quote(self.busybox), command) self.execute(command, timeout=1, as_root=as_root) except TimeoutError: pass @@ -1138,14 +1141,14 @@ class AndroidTarget(Target): # so we try the new version, and if it fails we use the old version. self.ls_command = 'ls -1' try: - self.execute('ls -1 {}'.format(self.working_directory), as_root=False) + self.execute('ls -1 {}'.format(quote(self.working_directory)), as_root=False) except TargetStableError: self.ls_command = 'ls' def list_directory(self, path, as_root=False): if self.ls_command == '': self.__setup_list_directory() - contents = self.execute('{} "{}"'.format(self.ls_command, escape_double_quotes(path)), as_root=as_root) + contents = self.execute('{} {}'.format(self.ls_command, quote(path)), as_root=as_root) return [x.strip() for x in contents.split('\n') if x.strip()] def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221 @@ -1193,7 +1196,7 @@ class AndroidTarget(Target): def capture_screen(self, filepath): on_device_file = self.path.join(self.working_directory, 'screen_capture.png') cmd = 'screencap -p {} && {} date -u -Iseconds' - ts = self.execute(cmd.format(on_device_file, self.busybox)).strip() + ts = self.execute(cmd.format(quote(on_device_file), quote(self.busybox))).strip() filepath = filepath.format(ts=ts) self.pull(on_device_file, filepath) self.remove(on_device_file) @@ -1284,14 +1287,14 @@ class AndroidTarget(Target): return output.split() def get_package_version(self, package): - output = self.execute('dumpsys package {}'.format(package)) + output = self.execute('dumpsys package {}'.format(quote(package))) for line in convert_new_lines(output).split('\n'): if 'versionName' in line: return line.split('=', 1)[1] return None def get_package_info(self, package): - output = self.execute('pm list packages -f {}'.format(package)) + output = self.execute('pm list packages -f {}'.format(quote(package))) for entry in output.strip().split('\n'): rest, entry_package = entry.rsplit('=', 1) if entry_package != package: @@ -1316,13 +1319,13 @@ class AndroidTarget(Target): if self.get_sdk_version() >= 23: flags.append('-g') # Grant all runtime permissions self.logger.debug("Replace APK = {}, ADB flags = '{}'".format(replace, ' '.join(flags))) - return adb_command(self.adb_name, "install {} '{}'".format(' '.join(flags), filepath), timeout=timeout) + return adb_command(self.adb_name, "install {} {}".format(' '.join(flags), quote(filepath)), timeout=timeout) else: raise TargetStableError('Can\'t install {}: unsupported format.'.format(filepath)) def grant_package_permission(self, package, permission): try: - return self.execute('pm grant {} {}'.format(package, permission)) + return self.execute('pm grant {} {}'.format(quote(package), quote(permission))) except TargetStableError as e: if 'is not a changeable permission type' in e.message: pass # Ignore if unchangeable @@ -1352,16 +1355,16 @@ class AndroidTarget(Target): """ Force a re-index of the mediaserver cache for the specified file. """ - command = 'am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://' - self.execute(command + filepath) + command = 'am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d {}' + self.execute(command.format(quote('file://' + filepath))) def broadcast_media_mounted(self, dirpath, as_root=False): """ Force a re-index of the mediaserver cache for the specified directory. """ - command = 'am broadcast -a android.intent.action.MEDIA_MOUNTED -d file://{} '\ + command = 'am broadcast -a android.intent.action.MEDIA_MOUNTED -d {} '\ '-n com.android.providers.media/.MediaScannerReceiver' - self.execute(command.format(dirpath), as_root=as_root) + self.execute(command.format(quote('file://'+dirpath)), as_root=as_root) def install_executable(self, filepath, with_name=None): self._ensure_executables_directory_is_writable() @@ -1370,14 +1373,14 @@ class AndroidTarget(Target): on_device_executable = self.path.join(self.executables_directory, executable_name) self.push(filepath, on_device_file) if on_device_file != on_device_executable: - self.execute('cp {} {}'.format(on_device_file, on_device_executable), as_root=self.needs_su) + self.execute('cp {} {}'.format(quote(on_device_file), quote(on_device_executable)), as_root=self.needs_su) self.remove(on_device_file, as_root=self.needs_su) - self.execute("chmod 0777 '{}'".format(on_device_executable), as_root=self.needs_su) + self.execute("chmod 0777 {}".format(quote(on_device_executable)), as_root=self.needs_su) self._installed_binaries[executable_name] = on_device_executable return on_device_executable def uninstall_package(self, package): - adb_command(self.adb_name, "uninstall {}".format(package), timeout=30) + adb_command(self.adb_name, "uninstall {}".format(quote(package)), timeout=30) def uninstall_executable(self, executable_name): on_device_executable = self.path.join(self.executables_directory, executable_name) @@ -1386,8 +1389,8 @@ class AndroidTarget(Target): def dump_logcat(self, filepath, filter=None, append=False, timeout=30): # pylint: disable=redefined-builtin op = '>>' if append else '>' - filtstr = ' -s {}'.format(filter) if filter else '' - command = 'logcat -d{} {} {}'.format(filtstr, op, filepath) + filtstr = ' -s {}'.format(quote(filter)) if filter else '' + command = 'logcat -d{} {} {}'.format(filtstr, op, quote(filepath)) adb_command(self.adb_name, command, timeout=timeout) def clear_logcat(self): @@ -1495,11 +1498,11 @@ class AndroidTarget(Target): self.set_rotation(3) def get_rotation(self): - cmd = 'settings get system user_rotation' - res = self.execute(cmd).strip() - try: - return int(res) - except ValueError: + output = self.execute('dumpsys input') + match = ANDROID_SCREEN_ROTATION_REGEX.search(output) + if match: + return int(match.group('rotation')) + else: return None def set_rotation(self, rotation): @@ -1520,13 +1523,13 @@ class AndroidTarget(Target): if it is already running :type force_new: bool """ - cmd = 'am start -a android.intent.action.VIEW -d "{}"' + cmd = 'am start -a android.intent.action.VIEW -d {}' if force_new: cmd = cmd + ' -f {}'.format(INTENT_FLAGS['ACTIVITY_NEW_TASK'] | INTENT_FLAGS['ACTIVITY_CLEAR_TASK']) - self.execute(cmd.format(escape_double_quotes(url))) + self.execute(cmd.format(quote(url))) def homescreen(self): self.execute('am start -a android.intent.action.MAIN -c android.intent.category.HOME') @@ -1546,8 +1549,8 @@ class AndroidTarget(Target): if matched: entry = sorted(matched, key=lambda x: len(x.mount_point))[-1] if 'rw' not in entry.options: - self.execute('mount -o rw,remount {} {}'.format(entry.device, - entry.mount_point), + self.execute('mount -o rw,remount {} {}'.format(quote(entry.device), + quote(entry.mount_point)), as_root=True) else: message = 'Could not find mount point for executables directory {}' diff --git a/external/devlib/devlib/trace/__init__.py b/external/devlib/devlib/trace/__init__.py index d55e9cf347256e6aacf848a1cdf4f76feb736150..09220377117375bb974a7473f475d68351facf7e 100644 --- a/external/devlib/devlib/trace/__init__.py +++ b/external/devlib/devlib/trace/__init__.py @@ -31,5 +31,13 @@ class TraceCollector(object): def stop(self): pass + def __enter__(self): + self.reset() + self.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.stop() + def get_trace(self, outfile): pass diff --git a/external/devlib/devlib/utils/android.py b/external/devlib/devlib/utils/android.py index ef3cb01641967f2aed676e1150288f84893d5191..bd221bddcd4ed196ff2b1972808e7fffdb36d483 100755 --- a/external/devlib/devlib/utils/android.py +++ b/external/devlib/devlib/utils/android.py @@ -28,10 +28,10 @@ import tempfile import subprocess from collections import defaultdict import pexpect +from pipes import quote from devlib.exception import TargetTransientError, TargetStableError, HostError from devlib.utils.misc import check_output, which, ABI_MAP -from devlib.utils.misc import escape_single_quotes, escape_double_quotes logger = logging.getLogger('android') @@ -235,7 +235,7 @@ class AdbConnection(object): def push(self, source, dest, timeout=None): if timeout is None: timeout = self.timeout - command = "push '{}' '{}'".format(source, dest) + command = "push {} {}".format(quote(source), quote(dest)) if not os.path.exists(source): raise HostError('No such file "{}"'.format(source)) return adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server) @@ -249,10 +249,10 @@ class AdbConnection(object): command = 'shell {} {}'.format(self.ls_command, source) output = adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server) for line in output.splitlines(): - command = "pull '{}' '{}'".format(line.strip(), dest) + command = "pull {} {}".format(quote(line.strip()), quote(dest)) adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server) return - command = "pull '{}' '{}'".format(source, dest) + command = "pull {} {}".format(quote(source), quote(dest)) return adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server) # pylint: disable=unused-argument @@ -285,7 +285,7 @@ class AdbConnection(object): def fastboot_command(command, timeout=None, device=None): _check_env() - target = '-s {}'.format(device) if device else '' + target = '-s {}'.format(quote(device)) if device else '' full_command = 'fastboot {} {}'.format(target, command) logger.debug(full_command) output, _ = check_output(full_command, timeout, shell=True) @@ -293,7 +293,7 @@ def fastboot_command(command, timeout=None, device=None): def fastboot_flash_partition(partition, path_to_image): - command = 'flash {} {}'.format(partition, path_to_image) + command = 'flash {} {}'.format(quote(partition), quote(path_to_image)) fastboot_command(command) @@ -341,7 +341,7 @@ def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS): tries += 1 if device: if "." in device: # Connect is required only for ADB-over-IP - command = 'adb connect {}'.format(device) + command = 'adb connect {}'.format(quote(device)) logger.debug(command) output, _ = check_output(command, shell=True, timeout=timeout) if _ping(device): @@ -368,7 +368,7 @@ def adb_disconnect(device): def _ping(device): _check_env() - device_string = ' -s {}'.format(device) if device else '' + device_string = ' -s {}'.format(quote(device)) if device else '' command = "adb{} shell \"ls /data/local/tmp > /dev/null\"".format(device_string) logger.debug(command) result = subprocess.call(command, stderr=subprocess.PIPE, shell=True) @@ -383,7 +383,7 @@ def adb_shell(device, command, timeout=None, check_exit_code=False, as_root=False, adb_server=None): # NOQA _check_env() if as_root: - command = 'echo \'{}\' | su'.format(escape_single_quotes(command)) + command = 'echo {} | su'.format(quote(command)) device_part = [] if adb_server: device_part = ['-H', adb_server] @@ -443,9 +443,9 @@ def adb_background_shell(device, command, """Runs the sepcified command in a subprocess, returning the the Popen object.""" _check_env() if as_root: - command = 'echo \'{}\' | su'.format(escape_single_quotes(command)) + command = 'echo {} | su'.format(quote(command)) device_string = ' -s {}'.format(device) if device else '' - full_command = 'adb{} shell "{}"'.format(device_string, escape_double_quotes(command)) + full_command = 'adb{} shell {}'.format(device_string, quote(command)) logger.debug(full_command) return subprocess.Popen(full_command, stdout=stdout, stderr=stderr, shell=True) @@ -610,9 +610,9 @@ class LogcatMonitor(object): # Logcat on older version of android do not support the -e argument # so fall back to using grep. if self.target.get_sdk_version() > 23: - logcat_cmd = '{} -e "{}"'.format(logcat_cmd, regexp) + logcat_cmd = '{} -e {}'.format(logcat_cmd, quote(regexp)) else: - logcat_cmd = '{} | grep "{}"'.format(logcat_cmd, regexp) + logcat_cmd = '{} | grep {}'.format(logcat_cmd, quote(regexp)) logcat_cmd = get_adb_command(self.target.conn.device, logcat_cmd) diff --git a/external/devlib/devlib/utils/misc.py b/external/devlib/devlib/utils/misc.py index c7ec8f123ff1ae1aa83b7420939f3c7a78f06bd4..54ba2b1cd8536ddb0a1eb1924bab9457925b0012 100644 --- a/external/devlib/devlib/utils/misc.py +++ b/external/devlib/devlib/utils/misc.py @@ -35,6 +35,7 @@ import subprocess import sys import threading import wrapt +import warnings from past.builtins import basestring @@ -416,24 +417,50 @@ def convert_new_lines(text): """ Convert new lines to a common format. """ return text.replace('\r\n', '\n').replace('\r', '\n') +def sanitize_cmd_template(cmd): + msg = ( + '''Quoted placeholder should not be used, as it will result in quoting the text twice. {} should be used instead of '{}' or "{}" in the template: ''' + ) + for unwanted in ('"{}"', "'{}'"): + if unwanted in cmd: + warnings.warn(msg + cmd, stacklevel=2) + cmd = cmd.replace(unwanted, '{}') + + return cmd def escape_quotes(text): - """Escape quotes, and escaped quotes, in the specified text.""" + """ + Escape quotes, and escaped quotes, in the specified text. + + .. note:: :func:`pipes.quote` should be favored where possible. + """ return re.sub(r'\\("|\')', r'\\\\\1', text).replace('\'', '\\\'').replace('\"', '\\\"') def escape_single_quotes(text): - """Escape single quotes, and escaped single quotes, in the specified text.""" + """ + Escape single quotes, and escaped single quotes, in the specified text. + + .. note:: :func:`pipes.quote` should be favored where possible. + """ return re.sub(r'\\("|\')', r'\\\\\1', text).replace('\'', '\'\\\'\'') def escape_double_quotes(text): - """Escape double quotes, and escaped double quotes, in the specified text.""" + """ + Escape double quotes, and escaped double quotes, in the specified text. + + .. note:: :func:`pipes.quote` should be favored where possible. + """ return re.sub(r'\\("|\')', r'\\\\\1', text).replace('\"', '\\\"') def escape_spaces(text): - """Escape spaces in the specified text""" + """ + Escape spaces in the specified text + + .. note:: :func:`pipes.quote` should be favored where possible. + """ return text.replace(' ', '\ ') diff --git a/external/devlib/devlib/utils/rendering.py b/external/devlib/devlib/utils/rendering.py index fa30b3f51dc4d8cb9ce754a95c52cfe695afc463..a72a72ea7e280a2e1f8063923ba5d4df03509670 100644 --- a/external/devlib/devlib/utils/rendering.py +++ b/external/devlib/devlib/utils/rendering.py @@ -21,6 +21,7 @@ import tempfile import threading import time from collections import namedtuple +from pipes import quote # pylint: disable=redefined-builtin from devlib.exception import WorkerThreadError, TargetNotRespondingError, TimeoutError @@ -138,8 +139,8 @@ class SurfaceFlingerFrameCollector(FrameCollector): self.target.execute('dumpsys SurfaceFlinger --latency-clear ') def get_latencies(self, activity): - cmd = 'dumpsys SurfaceFlinger --latency "{}"' - return self.target.execute(cmd.format(activity)) + cmd = 'dumpsys SurfaceFlinger --latency {}' + return self.target.execute(cmd.format(quote(activity))) def list(self): text = self.target.execute('dumpsys SurfaceFlinger --list') diff --git a/external/devlib/devlib/utils/serial_port.py b/external/devlib/devlib/utils/serial_port.py index 31ba790f01f54e5907f9296068d58375012479a3..d58d77fd9e966d78152a64f6ad91aa16ec9e2888 100644 --- a/external/devlib/devlib/utils/serial_port.py +++ b/external/devlib/devlib/utils/serial_port.py @@ -114,10 +114,13 @@ def open_serial_connection(timeout, get_conn=False, init_dtr=None, """ target, conn = get_connection(timeout, init_dtr=init_dtr, logcls=logcls, *args, **kwargs) + if get_conn: - yield target, conn + target_and_conn = (target, conn) else: - yield target + target_and_conn = target - target.close() # Closes the file descriptor used by the conn. - del conn + try: + yield target_and_conn + finally: + target.close() # Closes the file descriptor used by the conn. diff --git a/external/devlib/devlib/utils/ssh.py b/external/devlib/devlib/utils/ssh.py index e493ffbd6babf89c9e4f2a6c9adbb72eb1e1243e..74ece6609e29c732b3b4b8e74624a500753259c8 100644 --- a/external/devlib/devlib/utils/ssh.py +++ b/external/devlib/devlib/utils/ssh.py @@ -26,6 +26,8 @@ import socket import sys import time import atexit +from pipes import quote +from future.utils import raise_from # pylint: disable=import-error,wrong-import-position,ungrouped-imports,wrong-import-order import pexpect @@ -39,9 +41,7 @@ from pexpect import EOF, TIMEOUT, spawn # pylint: disable=redefined-builtin,wrong-import-position from devlib.exception import (HostError, TargetStableError, TargetNotRespondingError, TimeoutError, TargetTransientError) -from devlib.utils.misc import which, strip_bash_colors, check_output -from devlib.utils.misc import (escape_single_quotes, escape_double_quotes, - escape_spaces) +from devlib.utils.misc import which, strip_bash_colors, check_output, sanitize_cmd_template from devlib.utils.types import boolean @@ -169,7 +169,7 @@ class SshConnection(object): password_prompt=None, original_prompt=None, platform=None, - sudo_cmd="sudo -- sh -c '{}'" + sudo_cmd="sudo -- sh -c {}" ): self.host = host self.username = username @@ -178,22 +178,18 @@ class SshConnection(object): self.port = port self.lock = threading.Lock() self.password_prompt = password_prompt if password_prompt is not None else self.default_password_prompt - self.sudo_cmd = sudo_cmd + self.sudo_cmd = sanitize_cmd_template(sudo_cmd) logger.debug('Logging in {}@{}'.format(username, host)) timeout = timeout if timeout is not None else self.default_timeout self.conn = ssh_get_shell(host, username, password, self.keyfile, port, timeout, False, None) atexit.register(self.close) def push(self, source, dest, timeout=30): - dest = '"{}"@"{}":"{}"'.format(escape_double_quotes(self.username), - escape_spaces(escape_double_quotes(self.host)), - escape_spaces(escape_double_quotes(dest))) + dest = '{}@{}:{}'.format(self.username, self.host, dest) return self._scp(source, dest, timeout) def pull(self, source, dest, timeout=30): - source = '"{}"@"{}":"{}"'.format(escape_double_quotes(self.username), - escape_spaces(escape_double_quotes(self.host)), - escape_spaces(escape_double_quotes(source))) + source = '{}@{}:{}'.format(self.username, self.host, source) return self._scp(source, dest, timeout) def execute(self, command, timeout=None, check_exit_code=True, @@ -205,9 +201,13 @@ class SshConnection(object): try: with self.lock: _command = '({}); __devlib_ec=$?; echo; echo $__devlib_ec'.format(command) - raw_output = self._execute_and_wait_for_prompt( - _command, timeout, as_root, strip_colors) - output, exit_code_text, _ = raw_output.rsplit('\r\n', 2) + full_output = self._execute_and_wait_for_prompt(_command, timeout, as_root, strip_colors) + split_output = full_output.rsplit('\r\n', 2) + try: + output, exit_code_text, _ = split_output + except ValueError as e: + raise TargetStableError( + "cannot split reply (target misconfiguration?):\n'{}'".format(full_output)) if check_exit_code: try: exit_code = int(exit_code_text) @@ -236,7 +236,7 @@ class SshConnection(object): command = '{} {} {} {}@{} {}'.format(ssh, keyfile_string, port_string, self.username, self.host, command) logger.debug(command) if self.password: - command = _give_password(self.password, command) + command, _ = _give_password(self.password, command) return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True) except EOF: raise TargetNotRespondingError('Connection lost.') @@ -264,7 +264,7 @@ class SshConnection(object): # As we're already root, there is no need to use sudo. as_root = False if as_root: - command = self.sudo_cmd.format(escape_single_quotes(command)) + command = self.sudo_cmd.format(quote(command)) if log: logger.debug(command) self.conn.sendline(command) @@ -306,19 +306,18 @@ class SshConnection(object): # fails to connect to a device if port is explicitly specified using -P # option, even if it is the default port, 22. To minimize this problem, # only specify -P for scp if the port is *not* the default. - port_string = '-P {}'.format(self.port) if (self.port and self.port != 22) else '' - keyfile_string = '-i {}'.format(self.keyfile) if self.keyfile else '' - command = '{} -r {} {} {} {}'.format(scp, keyfile_string, port_string, source, dest) + port_string = '-P {}'.format(quote(str(self.port))) if (self.port and self.port != 22) else '' + keyfile_string = '-i {}'.format(quote(self.keyfile)) if self.keyfile else '' + command = '{} -r {} {} {} {}'.format(scp, keyfile_string, port_string, quote(source), quote(dest)) command_redacted = command logger.debug(command) if self.password: - command = _give_password(self.password, command) - command_redacted = command.replace(self.password, '') + command, command_redacted = _give_password(self.password, command) try: check_output(command, timeout=timeout, shell=True) except subprocess.CalledProcessError as e: - raise HostError("Failed to copy file with '{}'. Output:\n{}".format( - command_redacted, e.output)) + raise_from(HostError("Failed to copy file with '{}'. Output:\n{}".format( + command_redacted, e.output)), None) except TimeoutError as e: raise TimeoutError(command_redacted, e.output) @@ -452,13 +451,12 @@ class Gem5Connection(TelnetConnection): if os.path.basename(dest) != filename: dest = os.path.join(dest, filename) # Back to the gem5 world - self._gem5_shell("ls -al {}{}".format(self.gem5_input_dir, filename)) - self._gem5_shell("cat '{}''{}' > '{}'".format(self.gem5_input_dir, - filename, - dest)) + filename = quote(self.gem5_input_dir + filename) + self._gem5_shell("ls -al {}".format(filename)) + self._gem5_shell("cat {} > {}".format(filename, quote(dest))) self._gem5_shell("sync") - self._gem5_shell("ls -al {}".format(dest)) - self._gem5_shell("ls -al {}".format(self.gem5_input_dir)) + self._gem5_shell("ls -al {}".format(quote(dest))) + self._gem5_shell("ls -al {}".format(quote(self.gem5_input_dir))) logger.debug("Push complete.") def pull(self, source, dest, timeout=0): #pylint: disable=unused-argument @@ -487,8 +485,8 @@ class Gem5Connection(TelnetConnection): if os.path.isabs(source): if os.path.dirname(source) != self.execute('pwd', check_exit_code=False): - self._gem5_shell("cat '{}' > '{}'".format(filename, - dest_file)) + self._gem5_shell("cat {} > {}".format(quote(filename), + quote(dest_file))) self._gem5_shell("sync") self._gem5_shell("ls -la {}".format(dest_file)) logger.debug('Finished the copy in the simulator') @@ -759,7 +757,7 @@ class Gem5Connection(TelnetConnection): gem5_logger.debug("gem5_shell command: {}".format(command)) if as_root: - command = 'echo "{}" | su'.format(escape_double_quotes(command)) + command = 'echo {} | su'.format(quote(command)) # Send the actual command self.conn.send("{}\n".format(command)) @@ -871,7 +869,8 @@ class Gem5Connection(TelnetConnection): """ Internal method to check if the target has a certain file """ - command = 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi' + filepath = quote(filepath) + command = 'if [ -e {} ]; then echo 1; else echo 0; fi' output = self.execute(command.format(filepath), as_root=self.is_rooted) return boolean(output.strip()) @@ -927,8 +926,10 @@ class AndroidGem5Connection(Gem5Connection): def _give_password(password, command): if not sshpass: raise HostError('Must have sshpass installed on the host in order to use password-based auth.') - pass_string = "sshpass -p '{}' ".format(password) - return pass_string + command + pass_template = "sshpass -p {} " + pass_string = pass_template.format(quote(password)) + redacted_string = pass_template.format(quote('')) + return (pass_string + command, redacted_string + command) def _check_env(): diff --git a/external/devlib/doc/overview.rst b/external/devlib/doc/overview.rst index 35d3b18e3b8eb729a14cd32b06d992f260a2ae4a..8791739cd74ad522468f42c4c29cff7f89e54793 100644 --- a/external/devlib/doc/overview.rst +++ b/external/devlib/doc/overview.rst @@ -323,17 +323,14 @@ You can collected traces (currently, just ftrace) using # the buffer size to be used. trace = FtraceCollector(t, events=['power*'], buffer_size=40000) - # clear ftrace buffer - trace.reset() - - # start trace collection - trace.start() - - # Perform the operations you want to trace here... - import time; time.sleep(5) - - # stop trace collection - trace.stop() + # As a context manager, clear ftrace buffer using trace.reset(), + # start trace collection using trace.start(), then stop it Using + # trace.stop(). Using a context manager brings the guarantee that + # tracing will stop even if an exception occurs, including + # KeyboardInterrupt (ctr-C) and SystemExit (sys.exit) + with trace: + # Perform the operations you want to trace here... + import time; time.sleep(5) # extract the trace file from the target into a local file trace.get_trace('/tmp/trace.bin') diff --git a/external/devlib/setup.py b/external/devlib/setup.py index 3e1493e06914b9aeb17da0020010f23ad3ba6ff6..4571be28e4736ec2467bcefbda2261d12cd63bee 100644 --- a/external/devlib/setup.py +++ b/external/devlib/setup.py @@ -94,6 +94,7 @@ params = dict( 'pyserial', # Serial port interface 'wrapt', # Basic for construction of decorator functions 'future', # Python 2-3 compatibility + 'enum34;python_version<"3.4"', # Enums for Python < 3.4 'pandas', 'numpy', ], diff --git a/external/workload-automation/wa/commands/process.py b/external/workload-automation/wa/commands/process.py index 3ee9db1b85238eb92d4012ff3adf2c65ac00c60c..a1505d0bf30339c66a8ee086a82a605b15e3629b 100644 --- a/external/workload-automation/wa/commands/process.py +++ b/external/workload-automation/wa/commands/process.py @@ -80,6 +80,9 @@ class ProcessCommand(Command): pc = ProcessContext() for run_output in output_list: + pc.run_output = run_output + pc.target_info = run_output.target_info + if not args.recursive: self.logger.info('Installing output processors') else: @@ -108,8 +111,6 @@ class ProcessCommand(Command): pm.validate() pm.initialize(pc) - pc.run_output = run_output - pc.target_info = run_output.target_info for job_output in run_output.jobs: pc.job_output = job_output pm.enable_all() diff --git a/external/workload-automation/wa/commands/show.py b/external/workload-automation/wa/commands/show.py index 2641d0289fc0d2f49f390a4f54f952c48d0b0de2..3d673358aba28330b7404265e739a3c3bb26b124 100644 --- a/external/workload-automation/wa/commands/show.py +++ b/external/workload-automation/wa/commands/show.py @@ -20,6 +20,7 @@ import sys from subprocess import call, Popen, PIPE +from pipes import quote from wa import Command from wa.framework import pluginloader @@ -31,8 +32,6 @@ from wa.utils.doc import (strip_inlined_text, get_rst_from_plugin, get_params_rst, underline) from wa.utils.misc import which -from devlib.utils.misc import escape_double_quotes - class ShowCommand(Command): @@ -87,7 +86,7 @@ class ShowCommand(Command): title = '.TH {}{} 7'.format(kind, plugin_name) output = '\n'.join([title, body]) - call('echo "{}" | man -l -'.format(escape_double_quotes(output)), shell=True) + call('echo {} | man -l -'.format(quote(output)), shell=True) else: print(rst_output) # pylint: disable=superfluous-parens diff --git a/external/workload-automation/wa/framework/target/descriptor.py b/external/workload-automation/wa/framework/target/descriptor.py index 4f6a338a6edc0729c58cf773ca0f38cba76b61e9..5b9e51609aa881731bf6d5fbe6094127ddc777f2 100644 --- a/external/workload-automation/wa/framework/target/descriptor.py +++ b/external/workload-automation/wa/framework/target/descriptor.py @@ -345,9 +345,9 @@ CONNECTION_PARAMS = { """), Parameter( 'sudo_cmd', kind=str, - default="sudo -- sh -c '{}'", + default="sudo -- sh -c {}", description=""" - Sudo command to use. Must have ``"{}"`` specified + Sudo command to use. Must have ``{}`` specified somewhere in the string it indicate where the command to be run via sudo is to go. """), diff --git a/external/workload-automation/wa/utils/misc.py b/external/workload-automation/wa/utils/misc.py index b1e3cf55ca55561aca764b89ec19dead76959e69..6129a4c59e7cbcc5600accf01f67b33ea7e67e47 100644 --- a/external/workload-automation/wa/utils/misc.py +++ b/external/workload-automation/wa/utils/misc.py @@ -49,7 +49,6 @@ from devlib.exception import TargetError from devlib.utils.misc import (ABI_MAP, check_output, walk_modules, ensure_directory_exists, ensure_file_directory_exists, normalize, convert_new_lines, get_cpu_mask, unique, - escape_quotes, escape_single_quotes, escape_double_quotes, isiterable, getch, as_relative, ranges_to_list, memoized, list_to_ranges, list_to_mask, mask_to_list, which, to_identifier) diff --git a/external/workload-automation/wa/workloads/geekbench/__init__.py b/external/workload-automation/wa/workloads/geekbench/__init__.py index 14484d974f401aee44c00ebbbf5a4d6c90f1fce9..724c6074c01d6c3992c5cdd0272dcc23f0142c5a 100644 --- a/external/workload-automation/wa/workloads/geekbench/__init__.py +++ b/external/workload-automation/wa/workloads/geekbench/__init__.py @@ -52,6 +52,10 @@ class Geekbench(ApkUiautoWorkload): """ summary_metrics = ['score', 'multicore_score'] versions = { + '4.3.1': { + 'package': 'com.primatelabs.geekbench', + 'activity': '.HomeActivity', + }, '4.2.0': { 'package': 'com.primatelabs.geekbench', 'activity': '.HomeActivity', diff --git a/external/workload-automation/wa/workloads/geekbench/com.arm.wa.uiauto.geekbench.apk b/external/workload-automation/wa/workloads/geekbench/com.arm.wa.uiauto.geekbench.apk index 389e6b434d296f8140b8aff68af2f5022e0bb593..3e44831a82b7f790b1f2fd5574ea70c36d6a05e3 100644 Binary files a/external/workload-automation/wa/workloads/geekbench/com.arm.wa.uiauto.geekbench.apk and b/external/workload-automation/wa/workloads/geekbench/com.arm.wa.uiauto.geekbench.apk differ diff --git a/external/workload-automation/wa/workloads/geekbench/uiauto/app/src/main/java/com/arm/wa/uiauto/geekbench/UiAutomation.java b/external/workload-automation/wa/workloads/geekbench/uiauto/app/src/main/java/com/arm/wa/uiauto/geekbench/UiAutomation.java index ac9431fe74ef65cdee5c0a66114624b43d4fc7c8..f7f20ce593a71ceb297f56959cf306efbe49469f 100644 --- a/external/workload-automation/wa/workloads/geekbench/uiauto/app/src/main/java/com/arm/wa/uiauto/geekbench/UiAutomation.java +++ b/external/workload-automation/wa/workloads/geekbench/uiauto/app/src/main/java/com/arm/wa/uiauto/geekbench/UiAutomation.java @@ -126,7 +126,7 @@ public class UiAutomation extends BaseUiAutomation { public void runBenchmarks() throws Exception { UiObject runButton = - mDevice.findObject(new UiSelector().textContains("Run Benchmarks") + mDevice.findObject(new UiSelector().textContains("Run Benchmark") .className("android.widget.Button")); if (!runButton.waitForExists(WAIT_TIMEOUT_5SEC)) { throw new UiObjectNotFoundException("Could not find Run button"); diff --git a/external/workload-automation/wa/workloads/gfxbench/__init__.py b/external/workload-automation/wa/workloads/gfxbench/__init__.py index b34ece267e6a206901893a1a8e444458c9f2855e..a1fb23bd38a6c519b0b4e17f0d7648e8f0d54600 100755 --- a/external/workload-automation/wa/workloads/gfxbench/__init__.py +++ b/external/workload-automation/wa/workloads/gfxbench/__init__.py @@ -29,6 +29,7 @@ class Gfxbench(ApkUiautoWorkload): re.compile(r'1440p Manhattan 3.1 Offscreen score (.+)'), re.compile(r'Tessellation score (.+)'), re.compile(r'Tessellation Offscreen score (.+)')] + score_regex = re.compile(r'.*?([\d.]+).*') description = ''' Execute a subset of graphical performance benchmarks @@ -39,8 +40,7 @@ class Gfxbench(ApkUiautoWorkload): ''' parameters = [ Parameter('timeout', kind=int, default=3600, - description=('Timeout for a single iteration of the benchmark. This value is ' - 'multiplied by ``times`` to calculate the overall run timeout. ')), + description=('Timeout for an iteration of the benchmark.')), ] def __init__(self, target, **kwargs): @@ -55,10 +55,13 @@ class Gfxbench(ApkUiautoWorkload): for line in fh: for regex in self.regex_matches: match = regex.search(line) + # Check if we have matched the score string in logcat if match: - try: - result = float(match.group(1)) - except ValueError: + score_match = self.score_regex.search(match.group(1)) + # Check if there is valid number found for the score. + if score_match: + result = float(score_match.group(1)) + else: result = 'NaN' entry = regex.pattern.rsplit(None, 1)[0] context.add_metric(entry, result, 'FPS', lower_is_better=False) diff --git a/external/workload-automation/wa/workloads/gfxbench/com.arm.wa.uiauto.gfxbench.apk b/external/workload-automation/wa/workloads/gfxbench/com.arm.wa.uiauto.gfxbench.apk index 27b29bbcefb6aa29ace557c0536765ad1a2e04f3..da3f6377259af5a55cd6a8458443519f79943fb6 100755 Binary files a/external/workload-automation/wa/workloads/gfxbench/com.arm.wa.uiauto.gfxbench.apk and b/external/workload-automation/wa/workloads/gfxbench/com.arm.wa.uiauto.gfxbench.apk differ diff --git a/external/workload-automation/wa/workloads/gfxbench/uiauto/app/src/main/java/com/arm/wa/uiauto/gfxbench/UiAutomation.java b/external/workload-automation/wa/workloads/gfxbench/uiauto/app/src/main/java/com/arm/wa/uiauto/gfxbench/UiAutomation.java index 4acdab77efa6413b332e73d1b250031a684c5b3a..55e2a0d4e0f7c00bc10b1adb5d35a40faaeed9a6 100755 --- a/external/workload-automation/wa/workloads/gfxbench/uiauto/app/src/main/java/com/arm/wa/uiauto/gfxbench/UiAutomation.java +++ b/external/workload-automation/wa/workloads/gfxbench/uiauto/app/src/main/java/com/arm/wa/uiauto/gfxbench/UiAutomation.java @@ -49,6 +49,36 @@ public class UiAutomation extends BaseUiAutomation { public void setup() throws Exception{ setScreenOrientation(ScreenOrientation.NATURAL); clearFirstRun(); + + //Calculate the location of the test selection button + UiObject circle = + mDevice.findObject(new UiSelector().resourceId("net.kishonti.gfxbench.gl.v50000.corporate:id/main_circleControl") + .className("android.widget.RelativeLayout")); + Rect bounds = circle.getBounds(); + int selectx = bounds.width()/4; + selectx = bounds.centerX() + selectx; + int selecty = bounds.height()/4; + selecty = bounds.centerY() + selecty; + + Log.d(TAG, "maxx " + selectx); + Log.d(TAG, "maxy " + selecty); + + mDevice.click(selectx,selecty); + + //Disable the tests + toggleTest("High-Level Tests"); + toggleTest("Low-Level Tests"); + toggleTest("Special Tests"); + toggleTest("Fixed Time Test"); + + //Enable sub tests + toggleTest("Car Chase"); + toggleTest("1080p Car Chase Offscreen"); + toggleTest("Manhattan 3.1"); + toggleTest("1080p Manhattan 3.1 Offscreen"); + toggleTest("1440p Manhattan 3.1.1 Offscreen"); + toggleTest("Tessellation"); + toggleTest("1080p Tessellation Offscreen"); } @Test @@ -91,36 +121,6 @@ public class UiAutomation extends BaseUiAutomation { } public void runBenchmark() throws Exception { - //Calculate the location of the test selection button - UiObject circle = - mDevice.findObject(new UiSelector().resourceId("net.kishonti.gfxbench.gl.v50000.corporate:id/main_circleControl") - .className("android.widget.RelativeLayout")); - Rect bounds = circle.getBounds(); - int selectx = bounds.width()/4; - selectx = bounds.width() - selectx; - int selecty = bounds.height()/4; - selecty = bounds.centerY() + selecty; - - Log.d(TAG, "maxx " + selectx); - Log.d(TAG, "maxy " + selecty); - - mDevice.click(selectx,selecty); - - //Disable the tests - toggleTest("High-Level Tests"); - toggleTest("Low-Level Tests"); - toggleTest("Special Tests"); - toggleTest("Fixed Time Test"); - - //Enable sub tests - toggleTest("Car Chase"); - toggleTest("1080p Car Chase Offscreen"); - toggleTest("Manhattan 3.1"); - toggleTest("1080p Manhattan 3.1 Offscreen"); - toggleTest("1440p Manhattan 3.1.1 Offscreen"); - toggleTest("Tessellation"); - toggleTest("1080p Tessellation Offscreen"); - //Start the tests UiObject start = mDevice.findObject(new UiSelector().text("Start")); diff --git a/external/workload-automation/wa/workloads/glbenchmark/__init__.py b/external/workload-automation/wa/workloads/glbenchmark/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..dd38d95bef498ee876ebf67691e87d2029c0dafc --- /dev/null +++ b/external/workload-automation/wa/workloads/glbenchmark/__init__.py @@ -0,0 +1,144 @@ +# Copyright 2013-2015 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. +# + +# pylint: disable=E1101,E0203 +import re +import os + +from wa import ApkUiautoWorkload, Parameter, Alias +from wa.framework.exception import ConfigError + +# These maps provide use-friendly aliases for the most common options. +USE_CASE_MAP = { + 'egypt': 'GLBenchmark 2.5 Egypt HD', + 'egypt-classic': 'GLBenchmark 2.1 Egypt Classic', + 't-rex': 'GLBenchmark 2.7 T-Rex HD', +} + +TYPE_MAP = { + 'onscreen': 'C24Z16 Onscreen Auto', + 'offscreen': 'C24Z16 Offscreen Auto', +} + + +class Glb(ApkUiautoWorkload): + + name = 'glbenchmark' + description = """ + Measures the graphics performance of Android devices by testing + the underlying OpenGL (ES) implementation. + + http://gfxbench.com/about-gfxbench.jsp + + From the website: + + The benchmark includes console-quality high-level 3D animations + (T-Rex HD and Egypt HD) and low-level graphics measurements. + + With high vertex count and complex effects such as motion blur, parallax + mapping and particle systems, the engine of GFXBench stresses GPUs in order + provide users a realistic feedback on their device. + + """ + activity = 'com.glbenchmark.activities.GLBenchmarkDownloaderActivity' + view = 'com.glbenchmark.glbenchmark27/com.glbenchmark.activities.GLBRender' + + package_names = ['com.glbenchmark.glbenchmark27', 'com.glbenchmark.glbenchmark25'] + packages = { + '2.7': 'com.glbenchmark.glbenchmark27', + '2.5': 'com.glbenchmark.glbenchmark25', + } + # If usecase is not specified the default usecase is the first supported usecase alias + # for the specified version. + supported_usecase_aliases = { + '2.7': ['t-rex', 'egypt'], + '2.5': ['egypt-classic', 'egypt'], + } + + default_iterations = 1 + install_timeout = 500 + run_timeout = 4 * 60 + + regex = re.compile(r'GLBenchmark (metric|FPS): (.*)') + + parameters = [ + Parameter('version', default='2.7', allowed_values=['2.7', '2.5'], override=True, + description=('Specifies which version of the benchmark to run (different versions ' + 'support different use cases).')), + Parameter('use_case', default=None, + description="""Specifies which usecase to run, as listed in the benchmark menu; e.g. + ``'GLBenchmark 2.5 Egypt HD'``. For convenience, two aliases are provided + for the most common use cases: ``'egypt'`` and ``'t-rex'``. These could + be use instead of the full use case title. For version ``'2.7'`` it defaults + to ``'t-rex'``, for version ``'2.5'`` it defaults to ``'egypt-classic'``. + """), + Parameter('type', default='onscreen', + description="""Specifies which type of the use case to run, as listed in the benchmarks + menu (small text underneath the use case name); e.g. ``'C24Z16 Onscreen Auto'``. + For convenience, two aliases are provided for the most common types: + ``'onscreen'`` and ``'offscreen'``. These may be used instead of full type + names. + """), + Parameter('timeout', kind=int, default=200, + description="""Specifies how long, in seconds, UI automation will wait for results screen to + appear before assuming something went wrong. + """), + ] + + aliases = [ + Alias('glbench'), + Alias('egypt', use_case='egypt'), + Alias('t-rex', use_case='t-rex'), + Alias('egypt_onscreen', use_case='egypt', type='onscreen'), + Alias('t-rex_onscreen', use_case='t-rex', type='onscreen'), + Alias('egypt_offscreen', use_case='egypt', type='offscreen'), + Alias('t-rex_offscreen', use_case='t-rex', type='offscreen'), + ] + + def __init__(self, target, **kwargs): + super(Glb, self).__init__(target, **kwargs) + self.gui.uiauto_params['version'] = self.version + + if self.use_case is None: + self.use_case = self.supported_usecase_aliases[self.version][0] + if self.use_case.lower() in USE_CASE_MAP: + if self.use_case not in self.supported_usecase_aliases[self.version]: + raise ConfigError('usecases {} is not supported in version {}'.format(self.use_case, self.version)) + self.use_case = USE_CASE_MAP[self.use_case.lower()] + self.gui.uiauto_params['use_case'] = self.use_case.replace(' ', '_') + + if self.type.lower() in TYPE_MAP: + self.type = TYPE_MAP[self.type.lower()] + self.gui.uiauto_params['usecase_type'] = self.type.replace(' ', '_') + + self.gui.uiauto_params['timeout'] = self.run_timeout + self.package_names = [self.packages[self.version]] + + def update_output(self, context): + super(Glb, self).update_output(context) + match_count = 0 + with open(context.get_artifact_path('logcat')) as fh: + for line in fh: + match = self.regex.search(line) + if match: + metric = match.group(1) + value, units = match.group(2).split() + value = value.replace('*', '') + if metric == 'metric': + metric = 'Frames' + units = 'frames' + metric = metric + '_' + str(match_count // 2) + context.add_metric(metric, value, units) + match_count += 1 diff --git a/external/workload-automation/wa/workloads/glbenchmark/com.arm.wa.uiauto.glbenchmark.apk b/external/workload-automation/wa/workloads/glbenchmark/com.arm.wa.uiauto.glbenchmark.apk new file mode 100644 index 0000000000000000000000000000000000000000..2e50ec33ee7849f5f542203eff4696a63e3b9126 Binary files /dev/null and b/external/workload-automation/wa/workloads/glbenchmark/com.arm.wa.uiauto.glbenchmark.apk differ diff --git a/external/workload-automation/wa/workloads/glbenchmark/uiauto/app/build.gradle b/external/workload-automation/wa/workloads/glbenchmark/uiauto/app/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..9cd13954f6ee55702f3ddbd88af2390d57ce2e34 --- /dev/null +++ b/external/workload-automation/wa/workloads/glbenchmark/uiauto/app/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'com.android.application' + +def packageName = "com.arm.wa.uiauto.glbenchmark" + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.3" + defaultConfig { + applicationId "${packageName}" + minSdkVersion 18 + targetSdkVersion 25 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + applicationVariants.all { variant -> + variant.outputs.each { output -> + output.outputFile = file("$project.buildDir/apk/${packageName}.apk") + } + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support.test:runner:0.5' + compile 'com.android.support.test:rules:0.5' + compile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2' + compile(name: 'uiauto', ext:'aar') +} + +repositories { + flatDir { + dirs 'libs' + } +} diff --git a/external/workload-automation/wa/workloads/glbenchmark/uiauto/app/src/main/AndroidManifest.xml b/external/workload-automation/wa/workloads/glbenchmark/uiauto/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..e62f9cf1e586bd41a2a1c86fa5bec6f4d15f2772 --- /dev/null +++ b/external/workload-automation/wa/workloads/glbenchmark/uiauto/app/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/external/workload-automation/wa/workloads/glbenchmark/uiauto/app/src/main/java/com/arm/wa/uiauto/glbenchmark/UiAutomation.java b/external/workload-automation/wa/workloads/glbenchmark/uiauto/app/src/main/java/com/arm/wa/uiauto/glbenchmark/UiAutomation.java new file mode 100644 index 0000000000000000000000000000000000000000..34460ffae88cb284f14b8ada5519ae046fe854b5 --- /dev/null +++ b/external/workload-automation/wa/workloads/glbenchmark/uiauto/app/src/main/java/com/arm/wa/uiauto/glbenchmark/UiAutomation.java @@ -0,0 +1,167 @@ +/* Copyright 2013-2015 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. +*/ + + +package com.arm.wa.uiauto.glbenchmark; + +import android.app.Activity; +import android.os.Bundle; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.uiautomator.UiObject; +import android.support.test.uiautomator.UiObjectNotFoundException; +import android.support.test.uiautomator.UiScrollable; +import android.support.test.uiautomator.UiSelector; +import android.util.Log; + +import com.arm.wa.uiauto.BaseUiAutomation; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class UiAutomation extends BaseUiAutomation { + + public static String TAG = "glb"; + public static int maxScrolls = 15; + + private Bundle parameters; + private String version; + private String useCase; + private String type; + private int testTimeoutSeconds; + + + @Before + public void initialize() { + parameters = getParams(); + version = parameters.getString("version"); + useCase = parameters.getString("use_case").replace('_', ' '); + type = parameters.getString("usecase_type").replace('_', ' '); + testTimeoutSeconds = parameters.getInt("timeout"); + } + + @Test + public void setup() throws Exception { + dismissAndroidVersionPopup(); + goToPreformanceTestsMenu(); + selectUseCase(version, useCase, type); + } + @Test + public void runWorkload() throws Exception { + hitStart(); + waitForResults(version, useCase, testTimeoutSeconds); + } + + @Test + public void extractResults() throws Exception { + extractBenchmarkResults(); + } + + public void goToPreformanceTestsMenu() throws Exception { + UiSelector selector = new UiSelector(); + UiObject choosePerfTest = mDevice.findObject(selector.text("Performance Tests") + .className("android.widget.TextView")); + choosePerfTest.clickAndWaitForNewWindow(); + } + + public void selectUseCase(String version, String useCase, String type) throws Exception { + UiSelector selector = new UiSelector(); + UiScrollable testList = new UiScrollable(selector.className("android.widget.ListView")); + UiObject useCaseText = mDevice.findObject(selector.className("android.widget.TextView") + .text(useCase)); + if (version.equals("2.7")){ + UiObject typeText = useCaseText.getFromParent(selector.className("android.widget.TextView") + .text(type)); + int scrolls = 0; + while(!typeText.exists()) { + testList.scrollForward(); + scrolls += 1; + if (scrolls >= maxScrolls) { + break; + } + } + typeText.click(); + } + else if (version.equals("2.5")){ + int scrolls = 0; + while(!useCaseText.exists()) { + testList.scrollForward(); + scrolls += 1; + if (scrolls >= maxScrolls) { + break; + } + } + useCaseText.click(); + UiObject modeDisableModeButton = null; + if (type.contains("Onscreen")){ + modeDisableModeButton = mDevice.findObject(selector.text("Offscreen")); + } + else { + modeDisableModeButton = mDevice.findObject(selector.text("Onscreen")); + } + modeDisableModeButton.click(); + } + } + + public void hitStart() throws Exception { + UiSelector selector = new UiSelector(); + UiObject startButton = mDevice.findObject(selector.text("Start")); + startButton.clickAndWaitForNewWindow(); + } + + public void waitForResults(String version, String useCase, int timeout) throws Exception { + UiSelector selector = new UiSelector(); + UiObject results = null; + if (version.equals("2.7")) + results = mDevice.findObject(selector.text("Results").className("android.widget.TextView")); + else + results = mDevice.findObject(selector.text(useCase).className("android.widget.TextView")); + Log.v(TAG, "Waiting for results screen."); + // On some devices, the results screen sometimes gets "backgrounded" (or + // rather, doesn't seem to come to foreground to begin with). This code + // attemps to deal with that by explicitly bringing glbench to the + // foreground if results screen doesn't appear within testTimeoutSeconds seconds of + // starting GLB. + if (!results.waitForExists(TimeUnit.SECONDS.toMillis(timeout))) { + Log.v(TAG, "Results screen not found. Attempting to bring to foreground."); + String[] commandLine = {"am", "start", + "-a", "android.intent.action.MAIN", + "-c", "android.intent.category.LAUNCHER", + "-n", "com.glbenchmark.glbenchmark27/com.glbenchmark.activities.GLBenchmarkDownloaderActivity"}; + Process proc = Runtime.getRuntime().exec(commandLine); + proc.waitFor(); + Log.v(TAG, String.format("am start exit value: %d", proc.exitValue())); + if (!results.exists()) { + throw new UiObjectNotFoundException("Could not find results screen."); + } + } + Log.v(TAG, "Results screen found."); + } + + public void extractBenchmarkResults() throws Exception { + Log.v(TAG, "Extracting results."); + sleep(2); // wait for the results screen to fully load. + UiSelector selector = new UiSelector(); + UiObject fpsText = mDevice.findObject(selector.className("android.widget.TextView") + .textContains("fps")); + UiObject otherText = fpsText.getFromParent(selector.className("android.widget.TextView").index(0)); + + Log.v(TAG, String.format("GLBenchmark metric: %s", otherText.getText().replace('\n', ' '))); + Log.v(TAG, String.format("GLBenchmark FPS: %s", fpsText.getText().replace('\n', ' '))); + } +} diff --git a/external/workload-automation/wa/workloads/glbenchmark/uiauto/build.gradle b/external/workload-automation/wa/workloads/glbenchmark/uiauto/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..d0aa70430ccede160b7b023221e5caa096713b83 --- /dev/null +++ b/external/workload-automation/wa/workloads/glbenchmark/uiauto/build.gradle @@ -0,0 +1,23 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/external/workload-automation/wa/workloads/glbenchmark/uiauto/build.sh b/external/workload-automation/wa/workloads/glbenchmark/uiauto/build.sh new file mode 100755 index 0000000000000000000000000000000000000000..2bf6bf5b246f90cef6e92d6ddf936936e89d7c80 --- /dev/null +++ b/external/workload-automation/wa/workloads/glbenchmark/uiauto/build.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# CD into build dir if possible - allows building from any directory +script_path='.' +if `readlink -f $0 &>/dev/null`; then + script_path=`readlink -f $0 2>/dev/null` +fi +script_dir=`dirname $script_path` +cd $script_dir + +# Ensure gradelw exists before starting +if [[ ! -f gradlew ]]; then + echo 'gradlew file not found! Check that you are in the right directory.' + exit 9 +fi + +# Copy base class library from wa dist +libs_dir=app/libs +base_class=`python -c "import os, wa; print os.path.join(os.path.dirname(wa.__file__), 'framework', 'uiauto', 'uiauto.aar')"` +mkdir -p $libs_dir +cp $base_class $libs_dir + +# Build and return appropriate exit code if failed +# gradle build +./gradlew clean :app:assembleDebug +exit_code=$? +if [[ $exit_code -ne 0 ]]; then + echo "ERROR: 'gradle build' exited with code $exit_code" + exit $exit_code +fi + +# If successful move APK file to workload folder (overwrite previous) +package=com.arm.wa.uiauto.glbenchmark +rm -f ../$package +if [[ -f app/build/apk/$package.apk ]]; then + cp app/build/apk/$package.apk ../$package.apk +else + echo 'ERROR: UiAutomator apk could not be found!' + exit 9 +fi diff --git a/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradle/wrapper/gradle-wrapper.jar b/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 Binary files /dev/null and b/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradle/wrapper/gradle-wrapper.jar differ diff --git a/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradle/wrapper/gradle-wrapper.properties b/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..760fcce76a22cf8a100c3ffb95b70aac1c9a9916 --- /dev/null +++ b/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed May 03 15:42:44 BST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradlew b/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradlew new file mode 100755 index 0000000000000000000000000000000000000000..9d82f78915133e1c35a6ea51252590fb38efac2f --- /dev/null +++ b/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradlew.bat b/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradlew.bat new file mode 100644 index 0000000000000000000000000000000000000000..aec99730b4e8fcd90b57a0e8e01544fea7c31a89 --- /dev/null +++ b/external/workload-automation/wa/workloads/glbenchmark/uiauto/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/external/workload-automation/wa/workloads/glbenchmark/uiauto/settings.gradle b/external/workload-automation/wa/workloads/glbenchmark/uiauto/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..e7b4def49cb53d9aa04228dd3edb14c9e635e003 --- /dev/null +++ b/external/workload-automation/wa/workloads/glbenchmark/uiauto/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/lisa/energy_meter.py b/lisa/energy_meter.py index 685978f5275f9e89aeec584e03300c8b97bdb216..2a68a9ebeaacd771b5b06047295d3d92e7768fd9 100644 --- a/lisa/energy_meter.py +++ b/lisa/energy_meter.py @@ -283,6 +283,12 @@ class ACME(EnergyMeter): """ name = 'acme' + REPORT_DELAY_S = 2.0 + """ + iio-capture returns an empty string if killed right after its invocation, + so we have to enforce a delay between reset() and report() + """ + def __init__(self, target, conf, res_dir): super().__init__(target, res_dir) logger = self.get_logger() @@ -355,12 +361,14 @@ class ACME(EnergyMeter): csv_file = os.path.join(self._res_dir, 'samples_{}.csv'.format(channel)) # Start a dedicated iio-capture instance for this channel - self._iio[ch_id] = Popen([self._iiocapturebin, '-n', + self._iio[ch_id] = Popen(['stdbuf', '-i0', '-o0', '-e0', + self._iiocapturebin, '-n', self._hostname, '-o', '-c', '-f', str(csv_file), self._iio_device(channel)], - stdout=PIPE, stderr=STDOUT) + stdout=PIPE, stderr=STDOUT, + universal_newlines=True) # Wait some time before to check if there is any output sleep(1) @@ -384,6 +392,7 @@ class ACME(EnergyMeter): logger.debug('Started %s on %s...', self._iiocapturebin, self._str(channel)) + self.reset_time = time.monotonic() def report(self, out_dir, out_energy='energy.json'): """ @@ -395,6 +404,11 @@ class ACME(EnergyMeter): :param out_file: File name where to save energy data :type out_file: str """ + + delta = time.monotonic() - self.reset_time + if delta < self.REPORT_DELAY_S: + sleep(self.REPORT_DELAY_S - delta) + logger = self.get_logger() channels_nrg = {} channels_stats = {}