1
0
mirror of https://github.com/ARM-software/devlib.git synced 2025-09-22 20:01:53 +01:00

29 Commits

Author SHA1 Message Date
Marc Bonnici
002ade33a8 Version Bump 2019-07-19 16:37:04 +01:00
Marc Bonnici
2e8d42db79 setup.py Update classifiers 2019-07-19 16:37:04 +01:00
Pierre-Clément Tosi
6b414cc291 utils.adb_shell: Move from 'echo CMD | su' to '-c'
Move from the current implementation (piping the command to su) which
has unexpected behaviours to the '-c' su flag (which then becomes
required).
2019-07-19 16:36:01 +01:00
Pierre-Clément Tosi
0d798f1c4f utils.adb_shell: Improve stability (Py3)
Move from pipes.quote (private) to shlex.quote (Py3.3+ standard).

Make tests of inputs against None (their default value) instead of based
on their truthiness.

Improve logging through quoted commands (runnable as-is, less confusing).

Make the command-building process straightforward for readability and
maintainability.
2019-07-19 16:36:01 +01:00
Marc Bonnici
1325e59b1a target/KernelConfig: Implement the __bool__ method
To aid in checking whether any information is contained in the
`KernelConfig` ensure that that `__bool__` method value indicated the
presence of parsed input.
2019-07-18 15:12:30 +01:00
Marc Bonnici
f141899dae target/KernelConfig: Ensure get_config_name is static
`get_config_name` was previsouly treaded as a bound method so
ensure that is defined as static as expected.
2019-07-18 15:12:30 +01:00
Valentin Schneider
984556bc8e module/sched: Make SchedModule probing more accurate
Right now, this module won't be loaded if the sched_domain procfs
entries are not present on the target. However, other pieces of
information may be present in which case it would make sense to load
the module.

For instance, mainline kernels compiled without SCHED_DEBUG can still
expose the cpu_capacity sysfs entry. As such, try to get a better idea
of what's available and only disable the loading of the module if it
can provide absolutely nothing.
2019-07-09 15:36:13 +01:00
Valentin Schneider
03a469fc38 module/sched: Expose the remote CPU capacity sysfs path
A later change needs to access this outside of a SchedModule instance,
so make the information available as a classmethod.
2019-07-09 15:36:13 +01:00
Valentin Schneider
2d86474682 module/sched: Expose a classmethod variant of SchedModule.has_debug
A later change needs to access this outside of a SchedModule instance,
so make the information available as a classmethod.
2019-07-09 15:36:13 +01:00
Valentin Schneider
ada318f27b module/sched: Fix None check
As mentioned in the previous commit, CPU numbers would be passed to
SchedProcFSData's __init__() (instead of a proper sysfs path). When
done with CPU0, that path would be evaluated as False and the code
would carry on with the default path, which was quite confusing.

This has now been fixed (and 0 isn't such a great path to give
anyway), nevertheless this check should just catter to None.
2019-07-09 15:36:13 +01:00
Valentin Schneider
b8f7b24790 module/sched: Fix incorrect SchedProcFSData usage
Rather than using the conveniently provided `get_cpu_sd_info()` helper
method, `has_em()` and `get_em_capacity()` would build a
`SchedProcFSData` with `path=<CPU number>`, which is obviously broken.

Do the right thing and use `get_cpu_sd_info()` in those places.
2019-07-09 15:36:13 +01:00
Josh Choo
a9b9938b0f module/sched: Return the correct maximum capacity
The existing behaviour assumes that the cap_states file contains a list
of capacity|cost pairs, and attempts to return the maximum capacity by
selecting the value at the second last index of the list.

This assumption fails on some newer Qualcomm kernels where the
cap_states file contains a list of capacity|frequency|cost triplets.
Consequently, the maximum frequency would be erroneously returned
instead of the maximum capacity.

Fix the problem by dynamically calculating the index of the maximum
capacity by dividing the number of entries in cap_states by the value in
nr_cap_states.

---

For example, on a certain Snapdragon 845 device:

/proc/sys/kernel/sched_domain/cpu0/domain0/group0/energy/cap_states
        54 entries:

        CAP     FREQ    COST
        --------------------
        65	300000	12
        87	403200	17
        104	480000	21
        125	576000	27
        141	652800	31
        162	748800	37
        179     825600	42
        195	902400	47
        212	979200	52
        228	1056000	57
        245	1132800	62
        266	1228800	70
        286	1324800 78
        307	1420800	89
        328	1516800	103
        348	1612800	122
        365	1689600	141
        381	1766400	160

/proc/sys/kernel/sched_domain/cpu0/domain0/group0/energy/nr_cap_states
        18

Max capacity = 381 (third-last index)
2019-07-09 09:04:34 +01:00
Marc Bonnici
f619f1dd07 setup.py: Set maximum package version for python2.7 support
In the latest versions of panadas and numpy python2.7 support has been
dropped therefore restrict the maximum version of these packages.
2019-07-08 13:46:19 +01:00
Marc Bonnici
ad350c9267 bin/perf: Update binaries
In the previous version there appears to be a bug causing perf to
segfault as per https://github.com/ARM-software/devlib/issues/395.
Therefore update provided binaries to v3.19 which does not appear to
have this issue.
2019-06-11 13:05:37 +01:00
Douglas RAILLARD
8343794d34 module/thermal: Gracefully handle unexpected sysfs names
Instead of raising an exception, log an warning and carry on.
2019-06-05 15:52:20 +01:00
Douglas RAILLARD
f2bc5dbc14 devlib: Re-export DmesgCollector in devlib package
Allow using 'import devlib.DmesgCollector', just like
devlib.FtraceCollector.
2019-06-03 14:16:28 +01:00
Patrick Bellasi
6f42f67e95 target: Ensure we use installed binaries
Apart from busybox, devlib itself makes use of other system provided binaries.
For example, the DmesgCollector module uses the system provided dmesg.
In cases the system provided binary does not support some of the features
required by devlib, we currently just fails with an error.

For the user it is still possible to deploy a custom/updated version of a
required binary via the Target::install API. However, that binary is not
automatically considered by devlib.

Let's ensure that all Target::execute commands use a PATH which gives priority
to devlib installed binaries.

Signed-off-by: Patrick Bellasi <patrick.bellasi@arm.com>
2019-05-24 17:47:18 +01:00
Marc Bonnici
ae7f01fd19 target: Use root if available when determine number of cpus
On some targets some entries in `/sys/devices/system/cpu` require root
to list otherwise will return a permission error.
2019-05-24 11:18:54 +01:00
Pierre-Clément Tosi
b5f36610ad trace/perf: Soften POSIX signal for termination
Replace the default SIGKILL signal sent to perf to "request" its
termination by a SIGINT, allowing it to handle the signal by cleaning up
before exit. This should address issues regarding corrupted perf.data
output files.
2019-05-15 14:30:18 +01:00
Douglas RAILLARD
4c8f2430e2 trace: dmesg: Allow using old util-linux binary
Old util-linux binaries don't support --force-prefix. Multi-line entry
parsing will break on these, but at least we can collect the log.

Also decode the raw priority, so only the facility is not decoded in
case busybox or old util-linux is used.
2019-03-26 09:38:58 +00:00
Douglas RAILLARD
a8b6e56874 trace: dmesg: Call dmesg -c as root
Clearing the kernel ring buffer needs root permission.
2019-03-25 14:57:33 +00:00
Douglas RAILLARD
c92756d65a trace: Fix dmesg collector when using util-linux dmesg
Set missing "facility" attribute on DmesgCollector instances.
2019-03-25 14:57:33 +00:00
Douglas RAILLARD
8512f116fc trace: Add DmesgCollector
Allows collecting dmesg output and parses it for easy filtering.
2019-03-19 13:52:04 +00:00
Valentin Schneider
be8b87d559 module/sched: Fix/simplify procfs packing behaviour
Back when I first wrote this I tried to make something smart that
would automatically detect which procfs entries to pack into a
mapping, the condition to do so being "the entry ends with a
digit and there is another entry with the same name but a different
digit".

I wrongly assumed this would always work for the sched_domain entries,
but it's possible to have a domain with a single group and thus a
single "group0" entry.

Since we know which entries we want to pack, let's hard-code these and
be less smart about it.
2019-03-19 13:48:29 +00:00
Valentin Schneider
d76c2d63fe module/sched: Make get_capacities() work with hotplugged CPUs 2019-03-19 13:48:29 +00:00
Valentin Schneider
8bfa050226 module/sched: SchedProcFSData: Don't assume SD name is always present
The existence of that field is gated by SCHED_DEBUG, so look for an
always-present field instead.
2019-03-19 13:48:29 +00:00
Chris Redpath
8871fe3c25 devlib/sched: Change order of CPU capacity algorithms
There are two ways we can load CPU capacity. Up until 4.14, supported
kernels did not have the /sys/devices/system/cpu/cpuX/cpu_capacity file
and the only way to read cpu capacity was by grepping the EM from
procfs sched_domain output. After 4.14, that route still exists but is
complicated due to a change in the format once support for
frequency-power models was merged.

In order to avoid rewriting the procfs EM grepping code, lets switch the
order of algorithms we try to use when loading CPU capacity. All newer
kernels provide the dedicated sysfs node and all kernels which do not
have this node use the old format for the EM in sched_domain output.

Signed-off-by: Chris Redpath <chris.redpath@arm.com>
2019-03-18 14:29:38 +00:00
Sergei Trofimov
aa50b2d42d host: expect shell syntax inside LocalConnection.execute
When using sudo with LocalConnection, execute the input command via 'sh
-c' to ensure any shell syntax within the command is handled properly.
2019-03-07 09:34:23 +00:00
Marc Bonnici
ebcb1664e7 utils/version: Development version bump 2019-02-27 10:55:20 +00:00
12 changed files with 298 additions and 46 deletions

View File

@@ -48,6 +48,7 @@ from devlib.derived.fps import DerivedGfxInfoStats, DerivedSurfaceFlingerStats
from devlib.trace.ftrace import FtraceCollector
from devlib.trace.perf import PerfCollector
from devlib.trace.serial_trace import SerialTraceCollector
from devlib.trace.dmesg import DmesgCollector
from devlib.host import LocalConnection
from devlib.utils.android import AdbConnection

Binary file not shown.

Binary file not shown.

View File

@@ -71,7 +71,7 @@ class LocalConnection(object):
if self.unrooted:
raise TargetStableError('unrooted')
password = self._get_password()
command = 'echo {} | sudo -S '.format(quote(password)) + command
command = 'echo {} | sudo -S -- sh -c '.format(quote(password)) + quote(command)
ignore = None if check_exit_code else 'all'
try:
return check_output(command, shell=True, timeout=timeout, ignore=ignore)[0]

View File

@@ -52,6 +52,12 @@ class SchedProcFSNode(object):
_re_procfs_node = re.compile(r"(?P<name>.*\D)(?P<digits>\d+)$")
PACKABLE_ENTRIES = [
"cpu",
"domain",
"group"
]
@staticmethod
def _ends_with_digits(node):
if not isinstance(node, basestring):
@@ -71,18 +77,19 @@ class SchedProcFSNode(object):
"""
:returns: The name of the procfs node
"""
return re.search(SchedProcFSNode._re_procfs_node, node).group("name")
match = re.search(SchedProcFSNode._re_procfs_node, node)
if match:
return match.group("name")
@staticmethod
def _packable(node, entries):
return node
@classmethod
def _packable(cls, node):
"""
:returns: Whether it makes sense to pack a node into a common entry
"""
return (SchedProcFSNode._ends_with_digits(node) and
any([SchedProcFSNode._ends_with_digits(x) and
SchedProcFSNode._node_digits(x) != SchedProcFSNode._node_digits(node) and
SchedProcFSNode._node_name(x) == SchedProcFSNode._node_name(node)
for x in entries]))
SchedProcFSNode._node_name(node) in cls.PACKABLE_ENTRIES)
@staticmethod
def _build_directory(node_name, node_data):
@@ -119,7 +126,7 @@ class SchedProcFSNode(object):
# Find which entries can be packed into a common entry
packables = {
node : SchedProcFSNode._node_name(node) + "s"
for node in list(nodes.keys()) if SchedProcFSNode._packable(node, list(nodes.keys()))
for node in list(nodes.keys()) if SchedProcFSNode._packable(node)
}
self._dyn_attrs = {}
@@ -228,13 +235,13 @@ class SchedProcFSData(SchedProcFSNode):
# Even if we have a CPU entry, it can be empty (e.g. hotplugged out)
# Make sure some data is there
for cpu in cpus:
if target.file_exists(target.path.join(path, cpu, "domain0", "name")):
if target.file_exists(target.path.join(path, cpu, "domain0", "flags")):
return True
return False
def __init__(self, target, path=None):
if not path:
if path is None:
path = self.sched_domain_root
procfs = target.read_tree_values(path, depth=self._read_depth)
@@ -252,7 +259,21 @@ class SchedModule(Module):
logger = logging.getLogger(SchedModule.name)
SchedDomainFlag.check_version(target, logger)
return SchedProcFSData.available(target)
# It makes sense to load this module if at least one of those
# functionalities is enabled
schedproc = SchedProcFSData.available(target)
debug = SchedModule.target_has_debug(target)
dmips = any([target.file_exists(SchedModule.cpu_dmips_capacity_path(target, cpu))
for cpu in target.list_online_cpus()])
logger.info("Scheduler sched_domain procfs entries %s",
"found" if schedproc else "not found")
logger.info("Detected kernel compiled with SCHED_DEBUG=%s",
"y" if debug else "n")
logger.info("CPU capacity sysfs entries %s",
"found" if dmips else "not found")
return schedproc or debug or dmips
def get_kernel_attributes(self, matching=None, check_exit_code=True):
"""
@@ -306,12 +327,16 @@ class SchedModule(Module):
path = '/proc/sys/kernel/sched_' + attr
self.target.write_value(path, value, verify)
@classmethod
def target_has_debug(cls, target):
if target.config.get('SCHED_DEBUG') != 'y':
return False
return target.file_exists('/sys/kernel/debug/sched_features')
@property
@memoized
def has_debug(self):
if self.target.config.get('SCHED_DEBUG') != 'y':
return False;
return self.target.file_exists('/sys/kernel/debug/sched_features')
return self.target_has_debug(self.target)
def get_features(self):
"""
@@ -386,17 +411,26 @@ class SchedModule(Module):
:returns: Whether energy model data is available for 'cpu'
"""
if not sd:
sd = SchedProcFSData(self.target, cpu)
sd = self.get_cpu_sd_info(cpu)
return sd.procfs["domain0"].get("group0", {}).get("energy", {}).get("cap_states") != None
@classmethod
def cpu_dmips_capacity_path(cls, target, cpu):
"""
:returns: The target sysfs path where the dmips capacity data should be
"""
return target.path.join(
cls.cpu_sysfs_root,
'cpu{}/cpu_capacity'.format(cpu))
@memoized
def has_dmips_capacity(self, cpu):
"""
:returns: Whether dmips capacity data is available for 'cpu'
"""
return self.target.file_exists(
self.target.path.join(self.cpu_sysfs_root, 'cpu{}/cpu_capacity'.format(cpu))
self.cpu_dmips_capacity_path(self.target, cpu)
)
@memoized
@@ -405,10 +439,13 @@ class SchedModule(Module):
:returns: The maximum capacity value exposed by the EAS energy model
"""
if not sd:
sd = SchedProcFSData(self.target, cpu)
sd = self.get_cpu_sd_info(cpu)
cap_states = sd.domains[0].groups[0].energy.cap_states
return int(cap_states.split('\t')[-2])
cap_states_list = cap_states.split('\t')
num_cap_states = sd.domains[0].groups[0].energy.nr_cap_states
max_cap_index = -1 * int(len(cap_states_list) / num_cap_states)
return int(cap_states_list[max_cap_index])
@memoized
def get_dmips_capacity(self, cpu):
@@ -416,14 +453,9 @@ class SchedModule(Module):
:returns: The capacity value generated from the capacity-dmips-mhz DT entry
"""
return self.target.read_value(
self.target.path.join(
self.cpu_sysfs_root,
'cpu{}/cpu_capacity'.format(cpu)
),
int
self.cpu_dmips_capacity_path(self.target, cpu), int
)
@memoized
def get_capacities(self, default=None):
"""
:param default: Default capacity value to find if no data is
@@ -434,16 +466,16 @@ class SchedModule(Module):
:raises RuntimeError: Raised when no capacity information is
found and 'default' is None
"""
cpus = list(range(self.target.number_of_cpus))
cpus = self.target.list_online_cpus()
capacities = {}
sd_info = self.get_sd_info()
for cpu in cpus:
if self.has_em(cpu, sd_info.cpus[cpu]):
capacities[cpu] = self.get_em_capacity(cpu, sd_info.cpus[cpu])
elif self.has_dmips_capacity(cpu):
if self.has_dmips_capacity(cpu):
capacities[cpu] = self.get_dmips_capacity(cpu)
elif self.has_em(cpu, sd_info.cpus[cpu]):
capacities[cpu] = self.get_em_capacity(cpu, sd_info.cpus[cpu])
else:
if default != None:
capacities[cpu] = default

View File

@@ -88,6 +88,9 @@ class ThermalModule(Module):
for entry in target.list_directory(self.thermal_root):
re_match = re.match('^(thermal_zone|cooling_device)([0-9]+)', entry)
if not re_match:
self.logger.warning('unknown thermal entry: %s', entry)
continue
if re_match.group(1) == 'thermal_zone':
self.add_thermal_zone(re_match.group(2))

View File

@@ -155,7 +155,7 @@ class Target(object):
def number_of_cpus(self):
num_cpus = 0
corere = re.compile(r'^\s*cpu\d+\s*$')
output = self.execute('ls /sys/devices/system/cpu')
output = self.execute('ls /sys/devices/system/cpu', as_root=self.is_rooted)
for entry in output.split():
if corere.match(entry):
num_cpus += 1
@@ -385,6 +385,9 @@ class Target(object):
def execute(self, command, timeout=None, check_exit_code=True,
as_root=False, strip_colors=True, will_succeed=False):
# Ensure to use deployed command when availables
if self.executables_directory:
command = "PATH={}:$PATH && {}".format(self.executables_directory, command)
return self.conn.execute(command, timeout=timeout,
check_exit_code=check_exit_code, as_root=as_root,
strip_colors=strip_colors, will_succeed=will_succeed)
@@ -2006,6 +2009,9 @@ class KernelConfig(object):
This class does not provide a Mapping API and only return string values.
"""
@staticmethod
def get_config_name(name):
return TypedKernelConfig.get_config_name(name)
def __init__(self, text):
# Expose typed_config as a non-private attribute, so that user code
@@ -2014,7 +2020,9 @@ class KernelConfig(object):
# Expose the original text for backward compatibility
self.text = text
get_config_name = TypedKernelConfig.get_config_name
def __bool__(self):
return bool(self.typed_config)
not_set_regex = TypedKernelConfig.not_set_regex
def iteritems(self):

198
devlib/trace/dmesg.py Normal file
View File

@@ -0,0 +1,198 @@
# Copyright 2019 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.
#
from __future__ import division
import re
from itertools import takewhile
from datetime import timedelta
from devlib.trace import TraceCollector
class KernelLogEntry(object):
"""
Entry of the kernel ring buffer.
:param facility: facility the entry comes from
:type facility: str
:param level: log level
:type level: str
:param timestamp: Timestamp of the entry
:type timestamp: datetime.timedelta
:param msg: Content of the entry
:type msg: str
"""
_TIMESTAMP_MSG_REGEX = re.compile(r'\[(.*?)\] (.*)')
_RAW_LEVEL_REGEX = re.compile(r'<([0-9]+)>(.*)')
_PRETTY_LEVEL_REGEX = re.compile(r'\s*([a-z]+)\s*:([a-z]+)\s*:\s*(.*)')
def __init__(self, facility, level, timestamp, msg):
self.facility = facility
self.level = level
self.timestamp = timestamp
self.msg = msg
@classmethod
def from_str(cls, line):
"""
Parses a "dmesg --decode" output line, formatted as following:
kern :err : [3618282.310743] nouveau 0000:01:00.0: systemd-logind[988]: nv50cal_space: -16
Or the more basic output given by "dmesg -r":
<3>[3618282.310743] nouveau 0000:01:00.0: systemd-logind[988]: nv50cal_space: -16
"""
def parse_raw_level(line):
match = cls._RAW_LEVEL_REGEX.match(line)
if not match:
raise ValueError('dmesg entry format not recognized: {}'.format(line))
level, remainder = match.groups()
levels = DmesgCollector.LOG_LEVELS
# BusyBox dmesg can output numbers that need to wrap around
level = levels[int(level) % len(levels)]
return level, remainder
def parse_pretty_level(line):
match = cls._PRETTY_LEVEL_REGEX.match(line)
facility, level, remainder = match.groups()
return facility, level, remainder
def parse_timestamp_msg(line):
match = cls._TIMESTAMP_MSG_REGEX.match(line)
timestamp, msg = match.groups()
timestamp = timedelta(seconds=float(timestamp.strip()))
return timestamp, msg
line = line.strip()
# If we can parse the raw prio directly, that is a basic line
try:
level, remainder = parse_raw_level(line)
facility = None
except ValueError:
facility, level, remainder = parse_pretty_level(line)
timestamp, msg = parse_timestamp_msg(remainder)
return cls(
facility=facility,
level=level,
timestamp=timestamp,
msg=msg.strip(),
)
def __str__(self):
facility = self.facility + ': ' if self.facility else ''
return '{facility}{level}: [{timestamp}] {msg}'.format(
facility=facility,
level=self.level,
timestamp=self.timestamp.total_seconds(),
msg=self.msg,
)
class DmesgCollector(TraceCollector):
"""
Dmesg output collector.
:param level: Minimum log level to enable. All levels that are more
critical will be collected as well.
:type level: str
:param facility: Facility to record, see dmesg --help for the list.
:type level: str
.. warning:: If BusyBox dmesg is used, facility and level will be ignored,
and the parsed entries will also lack that information.
"""
# taken from "dmesg --help"
# This list needs to be ordered by priority
LOG_LEVELS = [
"emerg", # system is unusable
"alert", # action must be taken immediately
"crit", # critical conditions
"err", # error conditions
"warn", # warning conditions
"notice", # normal but significant condition
"info", # informational
"debug", # debug-level messages
]
def __init__(self, target, level=LOG_LEVELS[-1], facility='kern'):
super(DmesgCollector, self).__init__(target)
if level not in self.LOG_LEVELS:
raise ValueError('level needs to be one of: {}'.format(
', '.join(self.LOG_LEVELS)
))
self.level = level
# Check if dmesg is the BusyBox one, or the one from util-linux in a
# recent version.
# Note: BusyBox dmesg does not support -h, but will still print the
# help with an exit code of 1
self.basic_dmesg = '--force-prefix' not in \
self.target.execute('dmesg -h', check_exit_code=False)
self.facility = facility
self.reset()
@property
def entries(self):
return self._parse_entries(self.dmesg_out)
@classmethod
def _parse_entries(cls, dmesg_out):
if not dmesg_out:
return []
else:
return [
KernelLogEntry.from_str(line)
for line in dmesg_out.splitlines()
]
def reset(self):
self.dmesg_out = None
def start(self):
self.reset()
# Empty the dmesg ring buffer
self.target.execute('dmesg -c', as_root=True)
def stop(self):
levels_list = list(takewhile(
lambda level: level != self.level,
self.LOG_LEVELS
))
levels_list.append(self.level)
if self.basic_dmesg:
cmd = 'dmesg -r'
else:
cmd = 'dmesg --facility={facility} --force-prefix --decode --level={levels}'.format(
levels=','.join(levels_list),
facility=self.facility,
)
self.dmesg_out = self.target.execute(cmd)
def get_trace(self, outfile):
with open(outfile, 'wt') as f:
f.write(self.dmesg_out + '\n')

View File

@@ -104,7 +104,11 @@ class PerfCollector(TraceCollector):
self.target.kick_off(command)
def stop(self):
self.target.killall('perf', signal='SIGINT',
as_root=self.target.is_rooted)
# perf doesn't transmit the signal to its sleep call so handled here:
self.target.killall('sleep', as_root=self.target.is_rooted)
# NB: we hope that no other "important" sleep is on-going
# pylint: disable=arguments-differ
def get_trace(self, outdir):

View File

@@ -31,7 +31,10 @@ import pexpect
import xml.etree.ElementTree
import zipfile
from pipes import quote
try:
from shlex import quote
except ImportError:
from pipes import quote
from devlib.exception import TargetTransientError, TargetStableError, HostError
from devlib.utils.misc import check_output, which, ABI_MAP
@@ -422,23 +425,23 @@ def _ping(device):
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(quote(command))
device_part = []
if adb_server:
device_part = ['-H', adb_server]
device_part += ['-s', device] if device else []
parts = ['adb']
if adb_server is not None:
parts += ['-H', adb_server]
if device is not None:
parts += ['-s', device]
parts += ['shell',
command if not as_root else 'su -c {}'.format(quote(command))]
logger.debug(' '.join(quote(part) for part in parts))
# On older combinations of ADB/Android versions, the adb host command always
# exits with 0 if it was able to run the command on the target, even if the
# command failed (https://code.google.com/p/android/issues/detail?id=3254).
# Homogenise this behaviour by running the command then echoing the exit
# code.
adb_shell_command = '({}); echo \"\n$?\"'.format(command)
actual_command = ['adb'] + device_part + ['shell', adb_shell_command]
logger.debug('adb {} shell {}'.format(' '.join(device_part), command))
parts[-1] += ' ; echo "\n$?"'
try:
raw_output, _ = check_output(actual_command, timeout, shell=False, combined_output=True)
raw_output, _ = check_output(parts, timeout, shell=False, combined_output=True)
except subprocess.CalledProcessError as e:
raise TargetStableError(str(e))

View File

@@ -21,7 +21,7 @@ from subprocess import Popen, PIPE
VersionTuple = namedtuple('Version', ['major', 'minor', 'revision', 'dev'])
version = VersionTuple(1, 1, 1, '')
version = VersionTuple(1, 1, 2, '')
def get_devlib_version():

View File

@@ -85,8 +85,10 @@ params = dict(
'wrapt', # Basic for construction of decorator functions
'future', # Python 2-3 compatibility
'enum34;python_version<"3.4"', # Enums for Python < 3.4
'pandas',
'numpy',
'numpy<=1.16.4; python_version<"3"',
'numpy; python_version>="3"',
'pandas<=0.24.2; python_version<"3"',
'pandas; python_version>"3"',
],
extras_require={
'daq': ['daqpower'],
@@ -96,10 +98,11 @@ params = dict(
},
# https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: Apache Software License',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
],
)