From 6d9a03ad8f4dde4a70748c2d0493c042e82c4773 Mon Sep 17 00:00:00 2001 From: Waleed El-Geresy Date: Mon, 7 Aug 2017 18:35:20 +0100 Subject: [PATCH] ReventWorkload: Move class to linux and add features The ReventWorkload class has been moved to the linux directory and two new features have been added: the option to run an idle workload and the option to specify a .teardown revent file as well as a .setup file which runs in the eponymous stage. --- wlauto/common/android/workload.py | 125 +++++---------------- wlauto/common/linux/workload.py | 165 ++++++++++++++++++++++++++++ wlauto/resource_getters/standard.py | 2 + 3 files changed, 192 insertions(+), 100 deletions(-) create mode 100644 wlauto/common/linux/workload.py diff --git a/wlauto/common/android/workload.py b/wlauto/common/android/workload.py index b83541d2..541b8b24 100755 --- a/wlauto/common/android/workload.py +++ b/wlauto/common/android/workload.py @@ -16,22 +16,19 @@ import os import sys import time -from math import ceil from distutils.version import LooseVersion from wlauto.core.extension import Parameter, ExtensionMeta, ListCollection from wlauto.core.workload import Workload -from wlauto.core.resource import NO_ONE -from wlauto.common.android.resources import ApkFile, ReventFile -from wlauto.common.resources import ExtensionAsset, Executable, File +from wlauto.common.android.resources import ApkFile +from wlauto.common.resources import ExtensionAsset, File from wlauto.exceptions import WorkloadError, ResourceError, DeviceError from wlauto.utils.android import (ApkInfo, ANDROID_NORMAL_PERMISSIONS, ANDROID_UNCHANGEABLE_PERMISSIONS, UNSUPPORTED_PACKAGES) from wlauto.utils.types import boolean, ParameterDict -from wlauto.utils.revent import ReventRecording import wlauto.utils.statedetect as state_detector -import wlauto.common.android.resources +from wlauto.common.linux.workload import ReventWorkload DELAY = 5 @@ -481,102 +478,8 @@ class ApkWorkload(Workload): if self.uninstall_apk: self.device.uninstall(self.package) - AndroidBenchmark = ApkWorkload # backward compatibility - -class ReventWorkload(Workload): - # pylint: disable=attribute-defined-outside-init - - def __init__(self, device, _call_super=True, **kwargs): - if _call_super: - Workload.__init__(self, device, **kwargs) - devpath = self.device.path - self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent') - self.setup_timeout = kwargs.get('setup_timeout', None) - self.run_timeout = kwargs.get('run_timeout', None) - self.revent_setup_file = None - self.revent_run_file = None - self.on_device_setup_revent = None - self.on_device_run_revent = None - self.statedefs_dir = None - - if self.check_states: - state_detector.check_match_state_dependencies() - - def setup(self, context): - self.revent_setup_file = context.resolver.get(ReventFile(self, 'setup')) - self.revent_run_file = context.resolver.get(ReventFile(self, 'run')) - devpath = self.device.path - self.on_device_setup_revent = devpath.join(self.device.working_directory, - os.path.split(self.revent_setup_file)[-1]) - self.on_device_run_revent = devpath.join(self.device.working_directory, - os.path.split(self.revent_run_file)[-1]) - self._check_revent_files(context) - default_setup_timeout = ceil(ReventRecording(self.revent_setup_file).duration) + 30 - default_run_timeout = ceil(ReventRecording(self.revent_run_file).duration) + 30 - self.setup_timeout = self.setup_timeout or default_setup_timeout - self.run_timeout = self.run_timeout or default_run_timeout - - Workload.setup(self, context) - self.device.killall('revent') - command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_setup_revent) - self.device.execute(command, timeout=self.setup_timeout) - - def run(self, context): - command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_run_revent) - self.logger.debug('Replaying {}'.format(os.path.basename(self.on_device_run_revent))) - self.device.execute(command, timeout=self.run_timeout) - self.logger.debug('Replay completed.') - - def update_result(self, context): - pass - - def teardown(self, context): - self.device.killall('revent') - self.device.delete_file(self.on_device_setup_revent) - self.device.delete_file(self.on_device_run_revent) - - def _check_revent_files(self, context): - # check the revent binary - revent_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent')) - if not os.path.isfile(revent_binary): - message = '{} does not exist. '.format(revent_binary) - message += 'Please build revent for your system and place it in that location' - raise WorkloadError(message) - if not self.revent_setup_file: - # pylint: disable=too-few-format-args - message = '{0}.setup.revent file does not exist, Please provide one for your device, {0}'.format(self.device.name) - raise WorkloadError(message) - if not self.revent_run_file: - # pylint: disable=too-few-format-args - message = '{0}.run.revent file does not exist, Please provide one for your device, {0}'.format(self.device.name) - raise WorkloadError(message) - - self.on_device_revent_binary = self.device.install_executable(revent_binary) - self.device.push_file(self.revent_run_file, self.on_device_run_revent) - self.device.push_file(self.revent_setup_file, self.on_device_setup_revent) - - def _check_statedetection_files(self, context): - try: - self.statedefs_dir = context.resolver.get(File(self, 'state_definitions')) - except ResourceError: - self.logger.warning("State definitions directory not found. Disabling state detection.") - self.check_states = False - - def check_state(self, context, phase): - try: - self.logger.info("\tChecking workload state...") - screenshotPath = os.path.join(context.output_directory, "screen.png") - self.device.capture_screen(screenshotPath) - stateCheck = state_detector.verify_state(screenshotPath, self.statedefs_dir, phase) - if not stateCheck: - raise WorkloadError("Unexpected state after setup") - except state_detector.StateDefinitionError as e: - msg = "State definitions or template files missing or invalid ({}). Skipping state detection." - self.logger.warning(msg.format(e.message)) - - class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark): supported_platforms = ['android'] @@ -731,6 +634,7 @@ class GameWorkload(ApkWorkload, ReventWorkload): view = 'SurfaceView' loading_time = 10 supported_platforms = ['android'] + setup_required = True parameters = [ Parameter('install_timeout', default=500, override=True), @@ -749,6 +653,8 @@ class GameWorkload(ApkWorkload, ReventWorkload): def __init__(self, device, **kwargs): # pylint: disable=W0613 ApkWorkload.__init__(self, device, **kwargs) ReventWorkload.__init__(self, device, _call_super=False, **kwargs) + if self.check_states: + state_detector.check_match_state_dependencies() self.logcat_process = None self.module_dir = os.path.dirname(sys.modules[self.__module__].__file__) self.revent_dir = os.path.join(self.module_dir, 'revent_files') @@ -823,3 +729,22 @@ class GameWorkload(ApkWorkload, ReventWorkload): self.device.busybox, ondevice_cache) self.device.execute(deploy_command, timeout=timeout, as_root=True) + + def _check_statedetection_files(self, context): + try: + self.statedefs_dir = context.resolver.get(File(self, 'state_definitions')) + except ResourceError: + self.logger.warning("State definitions directory not found. Disabling state detection.") + self.check_states = False # pylint: disable=W0201 + + def check_state(self, context, phase): + try: + self.logger.info("\tChecking workload state...") + screenshotPath = os.path.join(context.output_directory, "screen.png") + self.device.capture_screen(screenshotPath) + stateCheck = state_detector.verify_state(screenshotPath, self.statedefs_dir, phase) + if not stateCheck: + raise WorkloadError("Unexpected state after setup") + except state_detector.StateDefinitionError as e: + msg = "State definitions or template files missing or invalid ({}). Skipping state detection." + self.logger.warning(msg.format(e.message)) diff --git a/wlauto/common/linux/workload.py b/wlauto/common/linux/workload.py new file mode 100644 index 00000000..1d25ce6e --- /dev/null +++ b/wlauto/common/linux/workload.py @@ -0,0 +1,165 @@ +# Copyright 2017 ARM Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +from math import ceil + +from wlauto.core.extension import Parameter +from wlauto.core.workload import Workload +from wlauto.core.resource import NO_ONE +from wlauto.common.resources import Executable +from wlauto.common.android.resources import ReventFile +from wlauto.exceptions import WorkloadError +from wlauto.utils.revent import ReventRecording + +class ReventWorkload(Workload): + # pylint: disable=attribute-defined-outside-init + + description = """ + A workload for playing back revent recordings. You can supply three + different files: + + 1. {device_model}.setup.revent + 2. {device_model}.run.revent + 3. {device_model}.teardown.revent + + You may generate these files using the wa record command using the -s flag + to specify the stage (``setup``, ``run``, ``teardown``) + + You may also supply an 'idle_time' in seconds in place of the run file. + The ``run`` file may only be omitted if you choose to run this way, but + while running idle may supply ``setup`` and ``teardown`` files. + + To use a ``setup`` or ``teardown`` file set the setup_required and/or + teardown_required class attributes to True (default: False). + + N.B. This is the default description. You may overwrite this for your + workload to include more specific information. + + """ + + setup_required = False + teardown_required = False + + parameters = [ + Parameter( + 'idle_time', kind=int, default=None, + description=''' + The time you wish the device to remain idle for (if a value is + given then this overrides any .run revent file). + '''), + ] + + def __init__(self, device, _call_super=True, **kwargs): + if _call_super: + Workload.__init__(self, device, **kwargs) + self.setup_timeout = kwargs.get('setup_timeout', None) + self.run_timeout = kwargs.get('run_timeout', None) + self.teardown_timeout = kwargs.get('teardown_timeout', None) + self.revent_setup_file = None + self.revent_run_file = None + self.revent_teardown_file = None + self.on_device_setup_revent = None + self.on_device_run_revent = None + self.on_device_teardown_revent = None + self.statedefs_dir = None + + def initialize(self, context): + devpath = self.device.path + self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent') + + def setup(self, context): + devpath = self.device.path + if self.setup_required: + self.revent_setup_file = context.resolver.get(ReventFile(self, 'setup')) + if self.revent_setup_file: + self.on_device_setup_revent = devpath.join(self.device.working_directory, + os.path.split(self.revent_setup_file)[-1]) + duration = ReventRecording(self.revent_setup_file).duration + self.default_setup_timeout = ceil(duration) + 30 + if not self.idle_time: + self.revent_run_file = context.resolver.get(ReventFile(self, 'run')) + if self.revent_run_file: + self.on_device_run_revent = devpath.join(self.device.working_directory, + os.path.split(self.revent_run_file)[-1]) + self.default_run_timeout = ceil(ReventRecording(self.revent_run_file).duration) + 30 + if self.teardown_required: + self.revent_teardown_file = context.resolver.get(ReventFile(self, 'teardown')) + if self.revent_teardown_file: + self.on_device_teardown_revent = devpath.join(self.device.working_directory, + os.path.split(self.revent_teardown_file)[-1]) + duration = ReventRecording(self.revent_teardown_file).duration + self.default_teardown_timeout = ceil(duration) + 30 + self._check_revent_files(context) + + Workload.setup(self, context) + + if self.revent_setup_file is not None: + self.setup_timeout = self.setup_timeout or self.default_setup_timeout + self.device.killall('revent') + command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_setup_revent) + self.device.execute(command, timeout=self.setup_timeout) + self.logger.debug('Revent setup completed.') + + def run(self, context): + if not self.idle_time: + self.run_timeout = self.run_timeout or self.default_run_timeout + command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_run_revent) + self.logger.debug('Replaying {}'.format(os.path.basename(self.on_device_run_revent))) + self.device.execute(command, timeout=self.run_timeout) + self.logger.debug('Replay completed.') + else: + self.logger.info('Idling for ' + str(self.idle_time) + ' seconds.') + self.device.sleep(self.idle_time) + self.logger.info('Successfully did nothing for ' + str(self.idle_time) + ' seconds!') + + def update_result(self, context): + pass + + def teardown(self, context): + if self.revent_teardown_file is not None: + self.teardown_timeout = self.teardown_timeout or self.default_teardown_timeout + command = '{} replay {}'.format(self.on_device_revent_binary, + self.on_device_teardown_revent) + self.device.execute(command, timeout=self.teardown_timeout) + self.logger.debug('Replay completed.') + self.device.killall('revent') + if self.revent_setup_file is not None: + self.device.delete_file(self.on_device_setup_revent) + if not self.idle_time: + self.device.delete_file(self.on_device_run_revent) + if self.revent_teardown_file is not None: + self.device.delete_file(self.on_device_teardown_revent) + + def _check_revent_files(self, context): + # check the revent binary + revent_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent')) + if not os.path.isfile(revent_binary): + message = '{} does not exist. '.format(revent_binary) + message += 'Please build revent for your system and place it in that location' + raise WorkloadError(message) + if not self.revent_run_file and not self.idle_time: + # pylint: disable=too-few-format-args + message = 'It seems a {0}.run.revent file does not exist, '\ + 'Please provide one for your device: {0}.' + raise WorkloadError(message.format(self.device.name)) + + self.on_device_revent_binary = self.device.install_executable(revent_binary) + if self.revent_setup_file is not None: + self.device.push_file(self.revent_setup_file, self.on_device_setup_revent) + if not self.idle_time: + self.device.push_file(self.revent_run_file, self.on_device_run_revent) + if self.revent_teardown_file is not None: + self.device.push_file(self.revent_teardown_file, self.on_device_teardown_revent) diff --git a/wlauto/resource_getters/standard.py b/wlauto/resource_getters/standard.py index 0f948137..837d7ad7 100644 --- a/wlauto/resource_getters/standard.py +++ b/wlauto/resource_getters/standard.py @@ -90,12 +90,14 @@ class ReventGetter(ResourceGetter): self.resolver.register(self, 'revent', GetterPriority.package) def get(self, resource, **kwargs): + # name format: [model/device_name.stage.revent] device_model = resource.owner.device.get_device_model() wa_device_name = resource.owner.device.name for name in [device_model, wa_device_name]: if not name: continue filename = '.'.join([name, resource.stage, 'revent']).lower() + self.logger.debug('Trying to get {0}.'.format(str(filename))) location = _d(os.path.join(self.get_base_location(resource), 'revent_files')) for candidate in os.listdir(location): if candidate.lower() == filename.lower():