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

37 Commits
v1.3 ... v1.3.1

Author SHA1 Message Date
Marc Bonnici
78938cf243 version: Bump revision number 2021-04-19 11:02:53 +01:00
Marc Bonnici
4941a7183a setup.py: Update project description 2021-04-19 10:58:59 +01:00
Marc Bonnici
3ab9d23a4a setup.py: Add long description to package
Use the readme as the long description, this will be
displayed on the PyPi page.
2021-04-19 10:58:59 +01:00
Marc Bonnici
5cf18a7b3c docs: Add note to hostid about PowerPc64 devices
Add caveat for the hostid on PowerPC64 devices due to the library
used when linking the included binary.
2021-04-19 10:46:46 +01:00
Benjamin Crawford
5bfeae08f4 bin: update aarch64 and x86_64 busybox binaries
The original BusyBox binaries were statically linked
against glibc, which caused segmentation faults to occur
on __nss_* calls. This switches the libc implementation
to uClibc in both cases.

busybox ver bump to 1.32.1 for arm64 and x86_64

update x86_64-linux-uclibc busybox

update aarch64-linux-uclibc busybox
2021-04-16 18:25:06 +01:00
Marc Bonnici
a87a1df0fb bin/busybox: Update ppc64le busybox
Update the ppc64le busybox implementation to v1.32.1
to match other architecture binaries.

Note: This binary is linked with musl as glibc has been
reported to cause issues however uclibc does not appear
to be a support build configuration.
2021-04-16 18:24:12 +01:00
Marc Bonnici
3bf763688e bin/busybox: Update 32 bit busybox binaries
Update the arm32 and add i368 v1.32.1 busybox binaries statically linked
with uclibc rather than the previous version with glibc which
caused segfaults on some devices.
2021-04-16 18:24:12 +01:00
Vincent Donnefort
a5cced85ce module/hotplug: Extend hotplug support with 'fail' and 'states' interfaces
The Linux HP path is composed of a series of states the CPU has to execute
to perform a complete hotplug or hotunplug. Devlib now exposes this
information. get_state() gives the current state of a CPU. get_states()
gives the list of all the states that composes the HP path.

The Linux HP 'fail' interface allows to simulate a failure during the
hotplug path. It helps to test the rollback mechanism. Devlib exposes this
interface with the method fail().
2021-04-12 13:38:42 +01:00
Javi Merino
9f55ae7603 connection: PopenTransferManager: Reset last_sample when we start a new transfer
last_sample is initialized in the PopenTransferManager constructor.
However, there is only one PopenTransferManager instance, which is
initialized during the construction of AdbConnection.  Afterwards, the
transfer manager is reused for every file transfer, without going
through __init__().  Therefore, after pushing/pulling a big file, the
next file transfer has compares the current size to the last sample of
the previous file transfer.  This makes it believe that the transfer
is inactive.

Reinitialize last_sample every time we start a new transfer to avoid
this.
2021-04-09 18:44:55 +01:00
Marc Bonnici
e7bafd6e5b version: Bump dev version
Exposing additional target properties so bump the development
version accordingly.
2021-04-07 18:21:33 +01:00
Marc Bonnici
ca84124fae doc/target: Fix Typo 2021-04-07 18:21:33 +01:00
Marc Bonnici
1f41853341 docs/target: Add new Target attributes 2021-04-07 18:21:33 +01:00
Marc Bonnici
82a2f7d8b6 target: Expose hostname as target property 2021-04-07 18:21:33 +01:00
Marc Bonnici
2a633b783a target: Expose hostid as target property 2021-04-07 18:21:33 +01:00
douglas-raillard-arm
b6d1863e77 collector/dmesg: DmesgCollector: Avoid not rooted targets
Fail early if the target is not rooted, as root is required by
DmesgCollector.start() anyway.
2021-04-07 10:55:27 +01:00
Vincent Donnefort
bbc891341c module/cpufreq: Warn when cpuinfo doesn't reflect the cpufreq request
The cpufreq/scaling_* files reflect the policy configuration from a kernel
point of view. The actual frequency at which the CPU is running can be
found in cpuinfo_cur_freq. Warn when the requested frequency does not
match the actual one.

As the kernel does not provide any guarantee that the requested frequency
will be actually used (due to other part of the system such as thermal or
the firmware), printing a warning instead of raising an error seems more
suitable here.
2021-04-07 10:55:08 +01:00
Marc Bonnici
d14df074ee utils/android: Fix Typo 2021-03-31 11:03:59 +01:00
Marc Bonnici
81f9ee2c50 utils/android: Use busybox implementation of ps
On some older versions of ps the flags used to look up
the pids are not supported and results in no output.
Use the busybox implementation for consistency.
2021-03-31 11:03:59 +01:00
Marc Bonnici
09d0a0f500 docs: Fix Formatting / typos 2021-03-31 11:02:49 +01:00
Robert Freeman
fe2fe3ae04 collector/perf: run simpleperf report-sample in the target if requested
If simpleperf record is called with "-f N", we may want to run
"simpleperf report-sample" on the output to dump the periodic
records.  Let the PerfCollector() run report-sample in the target,
similar to how we run report.
2021-03-31 10:03:33 +01:00
Robert Freeman
4859e818fb collector/perf: Only kill sleep if running perf stat
The stop() command of the PerfCollector kills all sleep commands in
the target, saying "We hope that no other important sleep is
on-going".  This is only needed if we are running "perf stat",
simpleperf does not need this.  Background the
perf/simpleperf command and only kill all sleeps in that specific case.
2021-03-31 10:03:33 +01:00
douglas-raillard-arm
5d342044a2 host and ssh: Fix sudo invocation
Add -k to sudo invocation to avoid using cached credentials.

If cached credentials is used, sudo will not write a prompt again,
leading to the stderr fixup code to remove a char from stderr output.
2021-03-24 19:06:05 +00:00
douglas-raillard-arm
d953377ff3 doc: Extend doc of Target.background()
Add doc for parameters "force_locale" and "timeout".
2021-03-24 19:05:50 +00:00
douglas-raillard-arm
4f2d9fa66d target: Add Target.background(timeout=...) parameter
Use a Timer daemonic thread to cancel the command after the given
timeout.
2021-03-24 19:05:50 +00:00
douglas-raillard-arm
4e44863777 connection: Un-hardcode _KILL_TIMEOUT
Replace erroneous use of _KILL_TIMEOUT constant by the kill_timeout
parameter.
2021-03-23 15:40:28 +00:00
douglas-raillard-arm
6cabad14d0 connection: Make ConnectionBase.cancel() more robust
Check with poll() if the command is already finished first, to avoid
sending SIGKILL to unrelated processes due to PID recycling.

The race window still exists between the call to poll() and _cancel(),
but is reduced a great deal.
2021-03-23 15:40:28 +00:00
douglas-raillard-arm
31f7c1e8f9 connection: Remove trailing whitespace 2021-03-23 15:40:28 +00:00
douglas-raillard-arm
3bc98f855b target: Align Target.background() LC_ALL and PATH
Make Target.background() behave like Target.execute():

* Set LC_ALL if force_locale is provided (defaults to "C")
* Add the target bin folder to PATH
2021-03-23 15:40:28 +00:00
douglas-raillard-arm
d2b80ccaf9 modules/sched: Fix sched domain flags parsing
Recent kernels can have a space-separated list of textual flags rather
than a bitfield packed in an int.
2021-03-12 17:56:01 +00:00
douglas-raillard-arm
552040f390 devlib/collector/dmesg: handle CONFIG_SECURITY_DMESG_RESTRICT
Some kernels compiled with CONFIG_SECURITY_DMESG_RESTRICT can restrict
reading the dmesg buffer to root user as a security hardening measure.
Detect this case and use root accordingly.
2021-02-12 12:11:07 +00:00
Javi Merino
0d259be01b collector/perf: Run perf as root if the target is rooted
If you want to collect events by event id (eg. in simpleperf, "rNNN"
with NNN a number), you must run it as root.  Otherwise, simpleperf
fails with a SIGABRT.

Run simpleperf as root by default if the target is rooted to avoid
this.
2021-02-02 11:30:09 +00:00
Marc Bonnici
792101819a utils/version: Prevent installation failure on systems without git
On systems that do not have git installed devlib will currently fail
to install with a FileNotFound Exception. If git is not present then
we will not have a commit hash so just ignore this error.
2021-01-12 17:53:27 +00:00
Javi Merino
3b8317d42e target: increase dump_logcat timeout
If WA is connected to a phone via a slow connection, dump_logcat() may
timeout when dumping logcat after the job has finished:

    2021-01-11 09:38:16,277 DEBUG       android:         adb -s X.Y.Z.X:5555 logcat -d -v threadtime > wa_output/wk1-wkld-1/logcat.log
    2021-01-11 09:38:46,317 DEBUG        signal:         Sending error-logged from <ErrorSignalHandler (DEBUG)>
    2021-01-11 09:38:46,318 DEBUG        signal:         Disconnecting <bound method Executor._error_signalled_callback of executor> from error-logged(<class 'louie.sender.Any'>)
    2021-01-11 09:38:46,317 ERROR        signal:         Timed out: adb -s X.Y.Z.X:5555 logcat -d -v threadtime > wa_output/wk1-wkld-1/logcat.log
    2021-01-11 09:38:46,317 ERROR        signal:         OUTPUT:
    2021-01-11 09:38:46,317 ERROR        signal:
    2021-01-11 09:38:46,317 ERROR        signal:

Increase the timeout to prevent this.
2021-01-11 10:11:42 +00:00
Marc Bonnici
e3da419e5b utils/android: Switch to using the lxml module
Using dexdump from versions 30.0.1-30.0.3 of Android build tools
does not produce valid XML caused by certain APKs
Use the lxml module for parsing xml as it is more robust and
better equipped to handle errors in input.
2021-01-08 17:22:12 +00:00
Marc Bonnici
e251b158b2 utils/android: Reorder imports 2021-01-08 17:22:12 +00:00
Marc Bonnici
c0a5765da5 utils/android: Fix aapt discovery with unexpected structure
If there is an additional file or directory in the `build_tools` directory
then WA can fail to find a working version of aapt(2), ensure that at least
one of the binaries is a valid file path.
2021-01-08 17:22:12 +00:00
Marc Bonnici
b32f15bbdb utils/version: Bump to dev version 2020-12-11 16:42:57 +00:00
19 changed files with 309 additions and 99 deletions

Binary file not shown.

Binary file not shown.

BIN
devlib/bin/ppc64le/busybox Executable file → Normal file

Binary file not shown.

BIN
devlib/bin/x86/busybox Executable file

Binary file not shown.

Binary file not shown.

View File

@@ -20,6 +20,8 @@ from datetime import timedelta
from devlib.collector import (CollectorBase, CollectorOutput,
CollectorOutputEntry)
from devlib.target import KernelConfigTristate
from devlib.exception import TargetStableError
class KernelLogEntry(object):
@@ -152,6 +154,10 @@ class DmesgCollector(CollectorBase):
def __init__(self, target, level=LOG_LEVELS[-1], facility='kern'):
super(DmesgCollector, self).__init__(target)
if not target.is_rooted:
raise TargetStableError('Cannot collect dmesg on non-rooted target')
self.output_path = None
if level not in self.LOG_LEVELS:
@@ -167,6 +173,8 @@ class DmesgCollector(CollectorBase):
self.basic_dmesg = '--force-prefix' not in \
self.target.execute('dmesg -h', check_exit_code=False)
self.facility = facility
self.needs_root = bool(target.config.typed_config.get(
'CONFIG_SECURITY_DMESG_RESTRICT', KernelConfigTristate.NO))
self.reset()
@property
@@ -178,7 +186,7 @@ class DmesgCollector(CollectorBase):
def start(self):
self.reset()
# Empty the dmesg ring buffer
# Empty the dmesg ring buffer. This requires root in all cases
self.target.execute('dmesg -c', as_root=True)
def stop(self):
@@ -195,7 +203,7 @@ class DmesgCollector(CollectorBase):
facility=self.facility,
)
self.dmesg_out = self.target.execute(cmd)
self.dmesg_out = self.target.execute(cmd, as_root=self.needs_root)
def set_output(self, output_path):
self.output_path = output_path

View File

@@ -24,8 +24,9 @@ from devlib.collector import (CollectorBase, CollectorOutput,
from devlib.utils.misc import ensure_file_directory_exists as _f
PERF_COMMAND_TEMPLATE = '{binary} {command} {options} {events} sleep 1000 > {outfile} 2>&1 '
PERF_STAT_COMMAND_TEMPLATE = '{binary} {command} {options} {events} {sleep_cmd} > {outfile} 2>&1 '
PERF_REPORT_COMMAND_TEMPLATE= '{binary} report {options} -i {datafile} > {outfile} 2>&1 '
PERF_REPORT_SAMPLE_COMMAND_TEMPLATE= '{binary} report-sample {options} -i {datafile} > {outfile} '
PERF_RECORD_COMMAND_TEMPLATE= '{binary} record {options} {events} -o {outfile}'
PERF_DEFAULT_EVENTS = [
@@ -90,12 +91,16 @@ class PerfCollector(CollectorBase):
events=None,
optionstring=None,
report_options=None,
run_report_sample=False,
report_sample_options=None,
labels=None,
force_install=False):
super(PerfCollector, self).__init__(target)
self.force_install = force_install
self.labels = labels
self.report_options = report_options
self.run_report_sample = run_report_sample
self.report_sample_options = report_sample_options
self.output_path = None
# Validate parameters
@@ -138,17 +143,20 @@ class PerfCollector(CollectorBase):
self.target.remove(filepath)
filepath = self._get_target_file(label, 'rpt')
self.target.remove(filepath)
filepath = self._get_target_file(label, 'rptsamples')
self.target.remove(filepath)
def start(self):
for command in self.commands:
self.target.kick_off(command)
self.target.background(command, as_root=self.target.is_rooted)
def stop(self):
self.target.killall(self.perf_type, 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
if self.perf_type == "perf" and self.command == "stat":
# 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
def set_output(self, output_path):
self.output_path = output_path
@@ -164,6 +172,9 @@ class PerfCollector(CollectorBase):
self._wait_for_data_file_write(label, self.output_path)
path = self._pull_target_file_to_host(label, 'rpt', self.output_path)
output.append(CollectorOutputEntry(path, 'file'))
if self.run_report_sample:
report_samples_path = self._pull_target_file_to_host(label, 'rptsamples', self.output_path)
output.append(CollectorOutputEntry(report_samples_path, 'file'))
else:
path = self._pull_target_file_to_host(label, 'out', self.output_path)
output.append(CollectorOutputEntry(path, 'file'))
@@ -188,10 +199,12 @@ class PerfCollector(CollectorBase):
def _build_perf_stat_command(self, options, events, label):
event_string = ' '.join(['-e {}'.format(e) for e in events])
command = PERF_COMMAND_TEMPLATE.format(binary = self.binary,
sleep_cmd = 'sleep 1000' if self.perf_type == 'perf' else ''
command = PERF_STAT_COMMAND_TEMPLATE.format(binary = self.binary,
command = self.command,
options = options or '',
events = event_string,
sleep_cmd = sleep_cmd,
outfile = self._get_target_file(label, 'out'))
return command
@@ -202,6 +215,13 @@ class PerfCollector(CollectorBase):
outfile=self._get_target_file(label, 'rpt'))
return command
def _build_perf_report_sample_command(self, label):
command = PERF_REPORT_SAMPLE_COMMAND_TEMPLATE.format(binary=self.binary,
options=self.report_sample_options or '',
datafile=self._get_target_file(label, 'data'),
outfile=self._get_target_file(label, 'rptsamples'))
return command
def _build_perf_record_command(self, options, label):
event_string = ' '.join(['-e {}'.format(e) for e in self.events])
command = PERF_RECORD_COMMAND_TEMPLATE.format(binary=self.binary,
@@ -234,6 +254,9 @@ class PerfCollector(CollectorBase):
data_file_finished_writing = True
report_command = self._build_perf_report_command(self.report_options, label)
self.target.execute(report_command)
if self.run_report_sample:
report_sample_command = self._build_perf_report_sample_command(label)
self.target.execute(report_sample_command)
def _validate_events(self, events):
available_events_string = self.target.execute('{} list | {} cat'.format(self.perf_type, self.target.busybox))

View File

@@ -105,12 +105,20 @@ class BackgroundCommand(ABC):
"""
self.send_signal(signal.SIGKILL)
@abstractmethod
def cancel(self, kill_timeout=_KILL_TIMEOUT):
"""
Try to gracefully terminate the process by sending ``SIGTERM``, then
waiting for ``kill_timeout`` to send ``SIGKILL``.
"""
if self.poll() is None:
self._cancel(kill_timeout=kill_timeout)
@abstractmethod
def _cancel(self, kill_timeout):
"""
Method to override in subclasses to implement :meth:`cancel`.
"""
pass
@abstractmethod
def wait(self):
@@ -209,11 +217,11 @@ class PopenBackgroundCommand(BackgroundCommand):
def poll(self):
return self.popen.poll()
def cancel(self, kill_timeout=_KILL_TIMEOUT):
def _cancel(self, kill_timeout):
popen = self.popen
os.killpg(os.getpgid(popen.pid), signal.SIGTERM)
try:
popen.wait(timeout=_KILL_TIMEOUT)
popen.wait(timeout=kill_timeout)
except subprocess.TimeoutExpired:
os.killpg(os.getpgid(popen.pid), signal.SIGKILL)
@@ -266,7 +274,7 @@ class ParamikoBackgroundCommand(BackgroundCommand):
else:
return None
def cancel(self, kill_timeout=_KILL_TIMEOUT):
def _cancel(self, kill_timeout):
self.send_signal(signal.SIGTERM)
# Check if the command terminated quickly
time.sleep(10e-3)
@@ -340,10 +348,10 @@ class AdbBackgroundCommand(BackgroundCommand):
def poll(self):
return self.adb_popen.poll()
def cancel(self, kill_timeout=_KILL_TIMEOUT):
def _cancel(self, kill_timeout):
self.send_signal(signal.SIGTERM)
try:
self.adb_popen.wait(timeout=_KILL_TIMEOUT)
self.adb_popen.wait(timeout=kill_timeout)
except subprocess.TimeoutExpired:
self.send_signal(signal.SIGKILL)
self.adb_popen.kill()
@@ -436,7 +444,7 @@ class TransferManagerBase(ABC):
self.transfer_started.clear()
self.transfer_completed.clear()
self.transfer_aborted.clear()
def _monitor(self):
start_t = monotonic()
self.transfer_completed.wait(self.start_transfer_poll_delay)
@@ -458,6 +466,7 @@ class PopenTransferManager(TransferManagerBase):
if self.transfer:
self.transfer.cancel()
self.transfer = None
self.last_sample = None
def isactive(self):
size_fn = self._push_dest_size if self.direction == 'push' else self._pull_dest_size
@@ -469,8 +478,9 @@ class PopenTransferManager(TransferManagerBase):
def set_transfer_and_wait(self, popen_bg_cmd):
self.transfer = popen_bg_cmd
self.last_sample = None
ret = self.transfer.wait()
if ret and not self.transfer_aborted.is_set():
raise subprocess.CalledProcessError(ret, self.transfer.popen.args)
elif self.transfer_aborted.is_set():
@@ -520,4 +530,4 @@ class SSHTransferManager(TransferManagerBase):
self.to_transfer = args[1]
elif len(args) == 2: # For SFTPClient callbacks
self.transferred = args[0]
self.to_transfer = args[1]
self.to_transfer = args[1]

View File

@@ -102,7 +102,7 @@ class LocalConnection(ConnectionBase):
if self.unrooted:
raise TargetStableError('unrooted')
password = self._get_password()
command = "echo {} | sudo -p ' ' -S -- sh -c {}".format(quote(password), quote(command))
command = "echo {} | sudo -k -p ' ' -S -- sh -c {}".format(quote(password), quote(command))
ignore = None if check_exit_code else 'all'
try:
stdout, stderr = check_output(command, shell=True, timeout=timeout, ignore=ignore)
@@ -127,7 +127,7 @@ class LocalConnection(ConnectionBase):
password = self._get_password()
# The sudo prompt will add a space on stderr, but we cannot filter
# it out here
command = "echo {} | sudo -p ' ' -S -- sh -c {}".format(quote(password), quote(command))
command = "echo {} | sudo -k -p ' ' -S -- sh -c {}".format(quote(password), quote(command))
# Make sure to get a new PGID so PopenBackgroundCommand() can kill
# all sub processes that could be started without troubles.

View File

@@ -301,7 +301,7 @@ class CpufreqModule(Module):
except ValueError:
raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency))
def get_frequency(self, cpu):
def get_frequency(self, cpu, cpuinfo=False):
"""
Returns the current frequency currently set for the specified CPU.
@@ -309,12 +309,18 @@ class CpufreqModule(Module):
try to read the current frequency and the following exception will be
raised ::
:param cpuinfo: Read the value in the cpuinfo interface that reflects
the actual running frequency.
:raises: TargetStableError if for some reason the frequency could not be read.
"""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_cur_freq'.format(cpu)
sysfile = '/sys/devices/system/cpu/{}/cpufreq/{}'.format(
cpu,
'cpuinfo_cur_freq' if cpuinfo else 'scaling_cur_freq')
return self.target.read_int(sysfile)
def set_frequency(self, cpu, frequency, exact=True):
@@ -350,6 +356,10 @@ class CpufreqModule(Module):
raise TargetStableError('Can\'t set {} frequency; governor must be "userspace"'.format(cpu))
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_setspeed'.format(cpu)
self.target.write_value(sysfile, value, verify=False)
cpuinfo = self.get_frequency(cpu, cpuinfo=True)
if cpuinfo != value:
self.logger.warning(
'The cpufreq value has not been applied properly cpuinfo={} request={}'.format(cpuinfo, value))
except ValueError:
raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency))

View File

@@ -57,3 +57,23 @@ class HotplugModule(Module):
return
value = 1 if online else 0
self.target.write_value(path, value)
def _get_path(self, path):
return self.target.path.join(self.base_path,
path)
def fail(self, cpu, state):
path = self._get_path('cpu{}/hotplug/fail'.format(cpu))
return self.target.write_value(path, state)
def get_state(self, cpu):
path = self._get_path('cpu{}/hotplug/state'.format(cpu))
return self.target.read_value(path)
def get_states(self):
path = self._get_path('hotplug/states')
states_string = self.target.read_value(path)
return dict(
map(str.strip, string.split(':', 1))
for string in states_string.strip().splitlines()
)

View File

@@ -15,7 +15,6 @@
import logging
import re
from enum import Enum
from past.builtins import basestring
@@ -147,43 +146,44 @@ class SchedProcFSNode(object):
self._dyn_attrs[key] = self._build_node(key, nodes[key])
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):
class _SchedDomainFlag:
"""
Represents a sched domain flag
Backward-compatible emulation of the former :class:`enum.Enum` that will
work on recent kernels with dynamic sched domain flags name and no value
exposed.
"""
# 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"
# Only defined in Android
# https://android.googlesource.com/kernel/common/+/android-4.14/include/linux/sched/topology.h#29
SD_SHARE_CAP_STATES = 0x8000, "(Android only) Domain members share capacity state"
@classmethod
def check_version(cls, target, logger):
_INSTANCES = {}
"""
Dictionary storing the instances so that they can be compared with ``is``
operator.
"""
def __new__(cls, name, value, doc=None):
self = super().__new__(cls)
self.name = name
self._value = value
self.__doc__ = doc
return cls._INSTANCES.setdefault(self, self)
def __eq__(self, other):
# We *have to* check for "value" as well, otherwise it will be
# impossible to keep in the same set 2 instances with differing values.
return self.name == other.name and self._value == other._value
def __hash__(self):
return hash((self.name, self._value))
@property
def value(self):
value = self._value
if value is None:
raise AttributeError('The kernel does not expose the sched domain flag values')
else:
return value
@staticmethod
def check_version(target, logger):
"""
Check the target and see if its kernel version matches our view of the world
"""
@@ -197,24 +197,111 @@ class SchedDomainFlag(DocInt, Enum):
"but target is running v{}".format(ref_parts, parts)
)
def __str__(self):
return self.name
def __repr__(self):
return '<SchedDomainFlag: {}>'.format(self.name)
class _SchedDomainFlagMeta(type):
"""
Metaclass of :class:`SchedDomainFlag`.
Provides some level of emulation of :class:`enum.Enum` behavior for
backward compatibility.
"""
@property
def _flags(self):
return [
attr
for name, attr in self.__dict__.items()
if name.startswith('SD_')
]
def __getitem__(self, i):
return self._flags[i]
def __len__(self):
return len(self._flags)
# These would be provided by collections.abc.Sequence, but using it on a
# metaclass seems to have issues around __init_subclass__
def __iter__(self):
return iter(self._flags)
def __reversed__(self):
return reversed(self._flags)
def __contains__(self, x):
return x in self._flags
@property
def __members__(self):
return {flag.name: flag for flag in self._flags}
class SchedDomainFlag(_SchedDomainFlag, metaclass=_SchedDomainFlagMeta):
"""
Represents a sched domain flag.
.. note:: ``SD_*`` class attributes are deprecated, new code should never
test a given flag against one of these attributes with ``is`` (.e.g ``x
is SchedDomainFlag.SD_LOAD_BALANCE``. This is because the
``SD_LOAD_BALANCE`` flag exists in two flavors that are not equal: one
with a value (the class attribute) and one without (dynamically created
when parsing flags for new kernels). Old code ran on old kernels should
work fine though.
"""
# 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 = _SchedDomainFlag("SD_LOAD_BALANCE", 0x0001, "Do load balancing on this domain")
SD_BALANCE_NEWIDLE = _SchedDomainFlag("SD_BALANCE_NEWIDLE", 0x0002, "Balance when about to become idle")
SD_BALANCE_EXEC = _SchedDomainFlag("SD_BALANCE_EXEC", 0x0004, "Balance on exec")
SD_BALANCE_FORK = _SchedDomainFlag("SD_BALANCE_FORK", 0x0008, "Balance on fork, clone")
SD_BALANCE_WAKE = _SchedDomainFlag("SD_BALANCE_WAKE", 0x0010, "Balance on wakeup")
SD_WAKE_AFFINE = _SchedDomainFlag("SD_WAKE_AFFINE", 0x0020, "Wake task to waking CPU")
SD_ASYM_CPUCAPACITY = _SchedDomainFlag("SD_ASYM_CPUCAPACITY", 0x0040, "Groups have different max cpu capacities")
SD_SHARE_CPUCAPACITY = _SchedDomainFlag("SD_SHARE_CPUCAPACITY", 0x0080, "Domain members share cpu capacity")
SD_SHARE_POWERDOMAIN = _SchedDomainFlag("SD_SHARE_POWERDOMAIN", 0x0100, "Domain members share power domain")
SD_SHARE_PKG_RESOURCES = _SchedDomainFlag("SD_SHARE_PKG_RESOURCES", 0x0200, "Domain members share cpu pkg resources")
SD_SERIALIZE = _SchedDomainFlag("SD_SERIALIZE", 0x0400, "Only a single load balancing instance")
SD_ASYM_PACKING = _SchedDomainFlag("SD_ASYM_PACKING", 0x0800, "Place busy groups earlier in the domain")
SD_PREFER_SIBLING = _SchedDomainFlag("SD_PREFER_SIBLING", 0x1000, "Prefer to place tasks in a sibling domain")
SD_OVERLAP = _SchedDomainFlag("SD_OVERLAP", 0x2000, "Sched_domains of this level overlap")
SD_NUMA = _SchedDomainFlag("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 = _SchedDomainFlag("SD_SHARE_CAP_STATES", 0x8000, "(Android only) Domain members share capacity state")
class SchedDomain(SchedProcFSNode):
"""
Represents a sched domain as seen through procfs
"""
def __init__(self, nodes):
super(SchedDomain, self).__init__(nodes)
super().__init__(nodes)
obj_flags = set()
for flag in list(SchedDomainFlag):
if self.flags & flag.value == flag.value:
obj_flags.add(flag)
flags = self.flags
# Recent kernels now have a space-separated list of flags instead of a
# packed bitfield
if isinstance(flags, str):
flags = {
_SchedDomainFlag(name=name, value=None)
for name in flags.split()
}
else:
def has_flag(flags, flag):
return flags & flag.value == flag.value
self.flags = obj_flags
flags = {
flag
for flag in SchedDomainFlag
if has_flag(flags, flag)
}
self.flags = flags
class SchedProcFSData(SchedProcFSNode):

View File

@@ -135,6 +135,14 @@ class Target(object):
def kernel_version(self):
return KernelVersion(self.execute('{} uname -r -v'.format(quote(self.busybox))).strip())
@property
def hostid(self):
return int(self.execute('{} hostid'.format(self.busybox)).strip(), 16)
@property
def hostname(self):
return self.execute('{} hostname'.format(self.busybox)).strip()
@property
def os_version(self): # pylint: disable=no-self-use
return {}
@@ -536,10 +544,7 @@ class Target(object):
# execution
def execute(self, command, timeout=None, check_exit_code=True,
as_root=False, strip_colors=True, will_succeed=False,
force_locale='C'):
def _prepare_cmd(self, command, force_locale):
# Force the locale if necessary for more predictable output
if force_locale:
# Use an explicit export so that the command is allowed to be any
@@ -550,12 +555,26 @@ class Target(object):
if self.executables_directory:
command = "export PATH={}:$PATH && {}".format(quote(self.executables_directory), command)
return command
def execute(self, command, timeout=None, check_exit_code=True,
as_root=False, strip_colors=True, will_succeed=False,
force_locale='C'):
command = self._prepare_cmd(command, force_locale)
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)
def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
return self.conn.background(command, stdout, stderr, as_root)
def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False,
force_locale='C', timeout=None):
command = self._prepare_cmd(command, force_locale)
bg_cmd = self.conn.background(command, stdout, stderr, as_root)
if timeout is not None:
timer = threading.Timer(timeout, function=bg_cmd.cancel)
timer.daemon = True
timer.start()
return bg_cmd
def invoke(self, binary, args=None, in_directory=None, on_cpus=None,
redirect_stderr=False, as_root=False, timeout=30):
@@ -1667,7 +1686,7 @@ class AndroidTarget(Target):
self.remove(on_device_executable, as_root=self.needs_su)
def dump_logcat(self, filepath, filter=None, logcat_format=None, append=False,
timeout=30): # pylint: disable=redefined-builtin
timeout=60): # pylint: disable=redefined-builtin
op = '>>' if append else '>'
filtstr = ' -s {}'.format(quote(filter)) if filter else ''
formatstr = ' -v {}'.format(quote(logcat_format)) if logcat_format else ''

View File

@@ -20,18 +20,20 @@ Utility functions for working with Android devices through adb.
"""
# pylint: disable=E1103
import glob
import os
import re
import sys
import time
import logging
import tempfile
import subprocess
from collections import defaultdict
import os
import pexpect
import xml.etree.ElementTree
import zipfile
import re
import subprocess
import sys
import tempfile
import time
import uuid
import zipfile
from collections import defaultdict
from io import StringIO
from lxml import etree
try:
from shlex import quote
@@ -227,7 +229,10 @@ class ApkInfo(object):
command = [dexdump, '-l', 'xml', extracted]
dump = self._run(command)
xml_tree = xml.etree.ElementTree.fromstring(dump)
# Dexdump from build tools v30.0.X does not seem to produce
# valid xml from certain APKs so ignore errors and attempt to recover.
parser = etree.XMLParser(encoding='utf-8', recover=True)
xml_tree = etree.parse(StringIO(dump), parser)
package = next((i for i in xml_tree.iter('package')
if i.attrib['name'] == self.package), None)
@@ -577,7 +582,7 @@ def adb_background_shell(conn, command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
as_root=False):
"""Runs the sepcified command in a subprocess, returning the the Popen object."""
"""Runs the specified command in a subprocess, returning the the Popen object."""
device = conn.device
adb_server = conn.adb_server
@@ -598,7 +603,7 @@ def adb_background_shell(conn, command,
p = subprocess.Popen(full_command, stdout=stdout, stderr=stderr, shell=True)
# Out of band PID lookup, to avoid conflicting needs with stdout redirection
find_pid = 'ps -A -o pid,args | grep {}'.format(quote(uuid_var))
find_pid = '{} ps -A -o pid,args | grep {}'.format(conn.busybox, quote(uuid_var))
ps_out = conn.execute(find_pid)
pids = [
int(line.strip().split(' ', 1)[0])
@@ -734,11 +739,13 @@ def _discover_aapt(env):
aapt2_path = ''
versions = os.listdir(env.build_tools)
for version in reversed(sorted(versions)):
if not aapt2_path and not os.path.isfile(aapt2_path):
if not os.path.isfile(aapt2_path):
aapt2_path = os.path.join(env.build_tools, version, 'aapt2')
if not aapt_path and not os.path.isfile(aapt_path):
if not os.path.isfile(aapt_path):
aapt_path = os.path.join(env.build_tools, version, 'aapt')
aapt_version = 1
# Use latest available version for aapt/appt2 but ensure at least one is valid.
if os.path.isfile(aapt2_path) or os.path.isfile(aapt_path):
break
# Use aapt2 only if present and we have a suitable version

View File

@@ -59,7 +59,7 @@ from devlib.connection import (ConnectionBase, ParamikoBackgroundCommand, PopenB
SSHTransferManager)
DEFAULT_SSH_SUDO_COMMAND = "sudo -p ' ' -S -- sh -c {}"
DEFAULT_SSH_SUDO_COMMAND = "sudo -k -p ' ' -S -- sh -c {}"
ssh = None

View File

@@ -21,7 +21,7 @@ from subprocess import Popen, PIPE
VersionTuple = namedtuple('Version', ['major', 'minor', 'revision', 'dev'])
version = VersionTuple(1, 3, 0, '')
version = VersionTuple(1, 3, 1, '')
def get_devlib_version():
@@ -33,8 +33,11 @@ def get_devlib_version():
def get_commit():
p = Popen(['git', 'rev-parse', 'HEAD'], cwd=os.path.dirname(__file__),
stdout=PIPE, stderr=PIPE)
try:
p = Popen(['git', 'rev-parse', 'HEAD'], cwd=os.path.dirname(__file__),
stdout=PIPE, stderr=PIPE)
except FileNotFoundError:
return None
std, _ = p.communicate()
p.wait()
if p.returncode:

View File

@@ -147,7 +147,7 @@ Connection Types
.. class:: SshConnection(host, username, password=None, keyfile=None, port=22,\
timeout=None, platform=None, \
sudo_cmd="sudo -- sh -c {}", strict_host_check=True, \
use_scp=False, poll_transfers=False,
use_scp=False, poll_transfers=False, \
start_transfer_poll_delay=30, total_transfer_timeout=3600,\
transfer_poll_period=30)
@@ -177,7 +177,7 @@ Connection Types
:param platform: Specify the platform to be used. The generic :class:`~devlib.platform.Platform`
class is used by default.
:param sudo_cmd: Specify the format of the command used to grant sudo access.
:param strict_host_check: Specify the ssh connection parameter ``StrictHostKeyChecking``,
:param strict_host_check: Specify the ssh connection parameter ``StrictHostKeyChecking``,
:param use_scp: Use SCP for file transfers, defaults to SFTP.
:param poll_transfers: Specify whether file transfers should be polled. Polling
monitors the progress of file transfers and periodically

View File

@@ -125,10 +125,21 @@ Target
This is a dict that contains a mapping of OS version elements to their
values. This mapping is OS-specific.
.. attribute:: Target.hostname
A string containing the hostname of the target.
.. attribute:: Target.hostid
A numerical id used to represent the identity of the target.
.. note:: Currently on 64-bit PowerPC devices this id will always be 0. This is
due to the included busybox binary being linked with musl.
.. attribute:: Target.system_id
A unique identifier for the system running on the target. This identifier is
intended to be uninque for the combination of hardware, kernel, and file
intended to be unique for the combination of hardware, kernel, and file
system.
.. attribute:: Target.model
@@ -225,15 +236,16 @@ Target
If transfer polling is supported (ADB connections and SSH connections),
``poll_transfers`` is set in the connection, and a timeout is not specified,
the push will be polled for activity. Inactive transfers will be
cancelled. (See :ref:`connection-types`\ for more information on polling).
cancelled. (See :ref:`connection-types` for more information on polling).
:param source: path on the host
:param dest: path on the target
:param as_root: whether root is required. Defaults to false.
:param timeout: timeout (in seconds) for the transfer; if the transfer does
not complete within this period, an exception will be raised.
not complete within this period, an exception will be raised. Leave unset
to utilise transfer polling if enabled.
:param globbing: If ``True``, the ``source`` is interpreted as a globbing
pattern instead of being take as-is. If the pattern has mulitple
pattern instead of being take as-is. If the pattern has multiple
matches, ``dest`` must be a folder (or will be created as such if it
does not exists yet).
@@ -244,7 +256,7 @@ Target
If transfer polling is supported (ADB connections and SSH connections),
``poll_transfers`` is set in the connection, and a timeout is not specified,
the pull will be polled for activity. Inactive transfers will be
cancelled. (See :ref:`connection-types`\ for more information on polling).
cancelled. (See :ref:`connection-types` for more information on polling).
:param source: path on the target
:param dest: path on the host
@@ -252,7 +264,7 @@ Target
:param timeout: timeout (in seconds) for the transfer; if the transfer does
not complete within this period, an exception will be raised.
:param globbing: If ``True``, the ``source`` is interpreted as a globbing
pattern instead of being take as-is. If the pattern has mulitple
pattern instead of being take as-is. If the pattern has multiple
matches, ``dest`` must be a folder (or will be created as such if it
does not exists yet).
@@ -280,7 +292,7 @@ Target
command to get predictable output that can be more safely parsed.
If ``None``, no locale is prepended.
.. method:: Target.background(command [, stdout [, stderr [, as_root]]])
.. method:: Target.background(command [, stdout [, stderr [, as_root, [, force_locale [, timeout]]])
Execute the command on the target, invoking it via subprocess on the host.
This will return :class:`subprocess.Popen` instance for the command.
@@ -292,6 +304,12 @@ Target
this may be used to redirect it to an alternative file handle.
:param as_root: The command will be executed as root. This will fail on
unrooted targets.
:param force_locale: Prepend ``LC_ALL=<force_locale>`` in front of the
command to get predictable output that can be more safely parsed.
If ``None``, no locale is prepended.
:param timeout: Timeout (in seconds) for the execution of the command. When
the timeout expires, :meth:`BackgroundCommand.cancel` is executed to
terminate the command.
.. note:: This **will block the connection** until the command completes.
@@ -700,7 +718,7 @@ Android Target
.. method:: AndroidTarget.get_stay_on_mode()
Returns an integer between ``0`` and ``7`` representing the current
stay-on mode of the device.
stay-on mode of the device.
.. method:: AndroidTarget.ensure_screen_is_off(verify=True)

View File

@@ -69,9 +69,13 @@ for root, dirs, files in os.walk(devlib_dir):
filepaths = [os.path.join(root, f) for f in files]
data_files[package_name].extend([os.path.relpath(f, package_dir) for f in filepaths])
with open("README.rst", "r") as fh:
long_description = fh.read()
params = dict(
name='devlib',
description='A framework for automating workload execution and measurment collection on ARM devices.',
description='A library for interacting with and instrumentation of remote devices.',
long_description=long_description,
version=__version__,
packages=packages,
package_data=data_files,
@@ -92,6 +96,7 @@ params = dict(
'numpy; python_version>="3"',
'pandas<=0.24.2; python_version<"3"',
'pandas; python_version>"3"',
'lxml', # More robust xml parsing
],
extras_require={
'daq': ['daqpower>=2'],