mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-01-19 04:21:17 +00:00
3112eb0a9b
- added caseless_string type. This behaves exactly like a string, except this ignores case in comparisons. It does, however, preserve case. E.g. >>> s = caseless_string('Test') >>> s == 'test' True >>> print s Test - added list_of type generating function. This allows to dynamically generate type-safe list types based on an existing type. E.g. >>> list_of_bool = list_of(bool) >>> list_of_bool(['foo', 0, 1, '', True]) [True, False, True, False, True] - Update core_names Device Parameter to be of type caseless_string
419 lines
16 KiB
Python
419 lines
16 KiB
Python
# 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.
|
|
#
|
|
|
|
"""
|
|
Base classes for device interfaces.
|
|
|
|
:Device: The base class for all devices. This defines the interface that must be
|
|
implemented by all devices and therefore any workload and instrumentation
|
|
can always rely on.
|
|
:AndroidDevice: Implements most of the :class:`Device` interface, and extends it
|
|
with a number of Android-specific methods.
|
|
:BigLittleDevice: Subclasses :class:`AndroidDevice` to implement big.LITTLE-specific
|
|
runtime parameters.
|
|
:SimpleMulticoreDevice: Subclasses :class:`AndroidDevice` to implement homogeneous cores
|
|
device runtime parameters.
|
|
|
|
"""
|
|
|
|
import os
|
|
import imp
|
|
import string
|
|
from collections import OrderedDict
|
|
from contextlib import contextmanager
|
|
|
|
from wlauto.core.extension import Extension, ExtensionMeta, AttributeCollection, Parameter
|
|
from wlauto.exceptions import DeviceError, ConfigError
|
|
from wlauto.utils.types import list_of_integers, list_of, caseless_string
|
|
|
|
|
|
__all__ = ['RuntimeParameter', 'CoreParameter', 'Device', 'DeviceMeta']
|
|
|
|
|
|
class RuntimeParameter(object):
|
|
"""
|
|
A runtime parameter which has its getter and setter methods associated it
|
|
with it.
|
|
|
|
"""
|
|
|
|
def __init__(self, name, getter, setter,
|
|
getter_args=None, setter_args=None,
|
|
value_name='value', override=False):
|
|
"""
|
|
:param name: the name of the parameter.
|
|
:param getter: the getter method which returns the value of this parameter.
|
|
:param setter: the setter method which sets the value of this parameter. The setter
|
|
always expects to be passed one argument when it is called.
|
|
:param getter_args: keyword arguments to be used when invoking the getter.
|
|
:param setter_args: keyword arguments to be used when invoking the setter.
|
|
:param override: A ``bool`` that specifies whether a parameter of the same name further up the
|
|
hierarchy should be overridden. If this is ``False`` (the default), an exception
|
|
will be raised by the ``AttributeCollection`` instead.
|
|
|
|
"""
|
|
self.name = name
|
|
self.getter = getter
|
|
self.setter = setter
|
|
self.getter_args = getter_args or {}
|
|
self.setter_args = setter_args or {}
|
|
self.value_name = value_name
|
|
self.override = override
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
__repr__ = __str__
|
|
|
|
|
|
class CoreParameter(RuntimeParameter):
|
|
"""A runtime parameter that will get expanded into a RuntimeParameter for each core type."""
|
|
|
|
def get_runtime_parameters(self, core_names):
|
|
params = []
|
|
for core in set(core_names):
|
|
name = string.Template(self.name).substitute(core=core)
|
|
getter = string.Template(self.getter).substitute(core=core)
|
|
setter = string.Template(self.setter).substitute(core=core)
|
|
getargs = dict(self.getter_args.items() + [('core', core)])
|
|
setargs = dict(self.setter_args.items() + [('core', core)])
|
|
params.append(RuntimeParameter(name, getter, setter, getargs, setargs, self.value_name, self.override))
|
|
return params
|
|
|
|
|
|
class DeviceMeta(ExtensionMeta):
|
|
|
|
to_propagate = ExtensionMeta.to_propagate + [
|
|
('runtime_parameters', RuntimeParameter, AttributeCollection),
|
|
]
|
|
|
|
|
|
class Device(Extension):
|
|
"""
|
|
Base class for all devices supported by Workload Automation. Defines
|
|
the interface the rest of WA uses to interact with devices.
|
|
|
|
:name: Unique name used to identify the device.
|
|
:platform: The name of the device's platform (e.g. ``Android``) this may
|
|
be used by workloads and instrumentation to assess whether they
|
|
can run on the device.
|
|
:working_directory: a string of the directory which is
|
|
going to be used by the workloads on the device.
|
|
:binaries_directory: a string of the binary directory for
|
|
the device.
|
|
:has_gpu: Should be ``True`` if the device as a separate GPU, and
|
|
``False`` if graphics processing is done on a CPU.
|
|
|
|
.. note:: Pretty much all devices currently on the market
|
|
have GPUs, however this may not be the case for some
|
|
development boards.
|
|
|
|
:path_module: The name of one of the modules implementing the os.path
|
|
interface, e.g. ``posixpath`` or ``ntpath``. You can provide
|
|
your own implementation rather than relying on one of the
|
|
standard library modules, in which case you need to specify
|
|
the *full* path to you module. e.g. '/home/joebloggs/mypathimp.py'
|
|
:parameters: A list of RuntimeParameter objects. The order of the objects
|
|
is very important as the setters and getters will be called
|
|
in the order the RuntimeParameter objects inserted.
|
|
:active_cores: This should be a list of all the currently active cpus in
|
|
the device in ``'/sys/devices/system/cpu/online'``. The
|
|
returned list should be read from the device at the time
|
|
of read request.
|
|
|
|
"""
|
|
__metaclass__ = DeviceMeta
|
|
|
|
parameters = [
|
|
Parameter('core_names', kind=list_of(caseless_string), mandatory=True, default=None,
|
|
description="""
|
|
This is a list of all cpu cores on the device with each
|
|
element being the core type, e.g. ``['a7', 'a7', 'a15']``. The
|
|
order of the cores must match the order they are listed in
|
|
``'/sys/devices/system/cpu'``. So in this case, ``'cpu0'`` must
|
|
be an A7 core, and ``'cpu2'`` an A15.'
|
|
"""),
|
|
Parameter('core_clusters', kind=list_of_integers, mandatory=True, default=None,
|
|
description="""
|
|
This is a list indicating the cluster affinity of the CPU cores,
|
|
each element correponding to the cluster ID of the core coresponding
|
|
to it's index. E.g. ``[0, 0, 1]`` indicates that cpu0 and cpu1 are on
|
|
cluster 0, while cpu2 is on cluster 1.
|
|
"""),
|
|
]
|
|
|
|
runtime_parameters = []
|
|
|
|
# These must be overwritten by subclasses.
|
|
name = None
|
|
platform = None
|
|
default_working_directory = None
|
|
has_gpu = None
|
|
path_module = None
|
|
active_cores = None
|
|
|
|
def __init__(self, **kwargs): # pylint: disable=W0613
|
|
super(Device, self).__init__(**kwargs)
|
|
if not self.path_module:
|
|
raise NotImplementedError('path_module must be specified by the deriving classes.')
|
|
libpath = os.path.dirname(os.__file__)
|
|
modpath = os.path.join(libpath, self.path_module)
|
|
if not modpath.lower().endswith('.py'):
|
|
modpath += '.py'
|
|
try:
|
|
self.path = imp.load_source('device_path', modpath)
|
|
except IOError:
|
|
raise DeviceError('Unsupported path module: {}'.format(self.path_module))
|
|
|
|
def reset(self):
|
|
"""
|
|
Initiate rebooting of the device.
|
|
|
|
Added in version 2.1.3.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def boot(self, *args, **kwargs):
|
|
"""
|
|
Perform the seteps necessary to boot the device to the point where it is ready
|
|
to accept other commands.
|
|
|
|
Changed in version 2.1.3: no longer expected to wait until boot completes.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def connect(self, *args, **kwargs):
|
|
"""
|
|
Establish a connection to the device that will be used for subsequent commands.
|
|
|
|
Added in version 2.1.3.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def disconnect(self):
|
|
""" Close the established connection to the device. """
|
|
raise NotImplementedError()
|
|
|
|
def initialize(self, context, *args, **kwargs):
|
|
"""
|
|
Default implementation just calls through to init(). May be overriden by specialised
|
|
abstract sub-cleasses to implent platform-specific intialization without requiring
|
|
concrete implementations to explicitly invoke parent's init().
|
|
|
|
Added in version 2.1.3.
|
|
|
|
"""
|
|
self.init(context, *args, **kwargs)
|
|
|
|
def init(self, context, *args, **kwargs):
|
|
"""
|
|
Initialize the device. This method *must* be called after a device reboot before
|
|
any other commands can be issued, however it may also be called without rebooting.
|
|
|
|
It is up to device-specific implementations to identify what initialisation needs
|
|
to be preformed on a particular invocation. Bear in mind that no assumptions can be
|
|
made about the state of the device prior to the initiation of workload execution,
|
|
so full initialisation must be performed at least once, even if no reboot has occurred.
|
|
After that, the device-specific implementation may choose to skip initialization if
|
|
the device has not been rebooted; it is up to the implementation to keep track of
|
|
that, however.
|
|
|
|
All arguments are device-specific (see the documentation for the your device).
|
|
|
|
"""
|
|
pass
|
|
|
|
def ping(self):
|
|
"""
|
|
This must return successfully if the device is able to receive commands, or must
|
|
raise :class:`wlauto.exceptions.DeviceUnresponsiveError` if the device cannot respond.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def get_runtime_parameter_names(self):
|
|
return [p.name for p in self._expand_runtime_parameters()]
|
|
|
|
def get_runtime_parameters(self):
|
|
""" returns the runtime parameters that have been set. """
|
|
# pylint: disable=cell-var-from-loop
|
|
runtime_parameters = OrderedDict()
|
|
for rtp in self._expand_runtime_parameters():
|
|
if not rtp.getter:
|
|
continue
|
|
getter = getattr(self, rtp.getter)
|
|
rtp_value = getter(**rtp.getter_args)
|
|
runtime_parameters[rtp.name] = rtp_value
|
|
return runtime_parameters
|
|
|
|
def set_runtime_parameters(self, params):
|
|
"""
|
|
The parameters are taken from the keyword arguments and are specific to
|
|
a particular device. See the device documentation.
|
|
|
|
"""
|
|
runtime_parameters = self._expand_runtime_parameters()
|
|
rtp_map = {rtp.name.lower(): rtp for rtp in runtime_parameters}
|
|
|
|
params = OrderedDict((k.lower(), v) for k, v in params.iteritems())
|
|
|
|
expected_keys = rtp_map.keys()
|
|
if not set(params.keys()) <= set(expected_keys):
|
|
unknown_params = list(set(params.keys()).difference(set(expected_keys)))
|
|
raise ConfigError('Unknown runtime parameter(s): {}'.format(unknown_params))
|
|
|
|
for param in params:
|
|
rtp = rtp_map[param]
|
|
setter = getattr(self, rtp.setter)
|
|
args = dict(rtp.setter_args.items() + [(rtp.value_name, params[rtp.name.lower()])])
|
|
setter(**args)
|
|
|
|
def capture_screen(self, filepath):
|
|
"""Captures the current device screen into the specified file in a PNG format."""
|
|
raise NotImplementedError()
|
|
|
|
def get_properties(self, output_path):
|
|
"""Captures and saves the device configuration properties version and
|
|
any other relevant information. Return them in a dict"""
|
|
raise NotImplementedError()
|
|
|
|
def listdir(self, path, **kwargs):
|
|
""" List the contents of the specified directory. """
|
|
raise NotImplementedError()
|
|
|
|
def push_file(self, source, dest):
|
|
""" Push a file from the host file system onto the device. """
|
|
raise NotImplementedError()
|
|
|
|
def pull_file(self, source, dest):
|
|
""" Pull a file from device system onto the host file system. """
|
|
raise NotImplementedError()
|
|
|
|
def delete_file(self, filepath):
|
|
""" Delete the specified file on the device. """
|
|
raise NotImplementedError()
|
|
|
|
def file_exists(self, filepath):
|
|
""" Check if the specified file or directory exist on the device. """
|
|
raise NotImplementedError()
|
|
|
|
def get_pids_of(self, process_name):
|
|
""" Returns a list of PIDs of the specified process name. """
|
|
raise NotImplementedError()
|
|
|
|
def kill(self, pid, as_root=False):
|
|
""" Kill the process with the specified PID. """
|
|
raise NotImplementedError()
|
|
|
|
def killall(self, process_name, as_root=False):
|
|
""" Kill all running processes with the specified name. """
|
|
raise NotImplementedError()
|
|
|
|
def install(self, filepath, **kwargs):
|
|
""" Install the specified file on the device. What "install" means is device-specific
|
|
and may possibly also depend on the type of file."""
|
|
raise NotImplementedError()
|
|
|
|
def uninstall(self, filepath):
|
|
""" Uninstall the specified file on the device. What "uninstall" means is device-specific
|
|
and may possibly also depend on the type of file."""
|
|
raise NotImplementedError()
|
|
|
|
def execute(self, command, timeout=None, **kwargs):
|
|
"""
|
|
Execute the specified command command on the device and return the output.
|
|
|
|
:param command: Command to be executed on the device.
|
|
:param timeout: If the command does not return after the specified time,
|
|
execute() will abort with an error. If there is no timeout for
|
|
the command, this should be set to 0 or None.
|
|
|
|
Other device-specific keyword arguments may also be specified.
|
|
|
|
:returns: The stdout output from the command.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def set_sysfile_value(self, filepath, value, verify=True):
|
|
"""
|
|
Write the specified value to the specified file on the device
|
|
and verify that the value has actually been written.
|
|
|
|
:param file: The file to be modified.
|
|
:param value: The value to be written to the file. Must be
|
|
an int or a string convertable to an int.
|
|
:param verify: Specifies whether the value should be verified, once written.
|
|
|
|
Should raise DeviceError if could write value.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def get_sysfile_value(self, sysfile, kind=None):
|
|
"""
|
|
Get the contents of the specified sysfile.
|
|
|
|
:param sysfile: The file who's contents will be returned.
|
|
|
|
:param kind: The type of value to be expected in the sysfile. This can
|
|
be any Python callable that takes a single str argument.
|
|
If not specified or is None, the contents will be returned
|
|
as a string.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def start(self):
|
|
"""
|
|
This gets invoked before an iteration is started and is endented to help the
|
|
device manange any internal supporting functions.
|
|
|
|
"""
|
|
pass
|
|
|
|
def stop(self):
|
|
"""
|
|
This gets invoked after iteration execution has completed and is endented to help the
|
|
device manange any internal supporting functions.
|
|
|
|
"""
|
|
pass
|
|
|
|
def __str__(self):
|
|
return 'Device<{}>'.format(self.name)
|
|
|
|
__repr__ = __str__
|
|
|
|
def _expand_runtime_parameters(self):
|
|
expanded_params = []
|
|
for param in self.runtime_parameters:
|
|
if isinstance(param, CoreParameter):
|
|
expanded_params.extend(param.get_runtime_parameters(self.core_names)) # pylint: disable=no-member
|
|
else:
|
|
expanded_params.append(param)
|
|
return expanded_params
|
|
|
|
@contextmanager
|
|
def _check_alive(self):
|
|
try:
|
|
yield
|
|
except Exception as e:
|
|
self.ping()
|
|
raise e
|
|
|