From e60e31ff8e3197038b6ffcb3d9b87b6ecb5612b1 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Fri, 7 Apr 2017 11:02:02 +0100
Subject: [PATCH 01/14] Imports: Updated Imports

Switch remaining wlauto imports to wa.
Added other missing imports.
---
 wa/commands/record.py                  | 6 ++++--
 wa/framework/configuration/__init__.py | 9 ++++-----
 wa/framework/configuration/default.py  | 8 ++++----
 wa/framework/host.py                   | 2 +-
 wa/framework/plugin.py                 | 8 ++++----
 wa/framework/signal.py                 | 6 +++---
 wa/framework/workload.py               | 3 ++-
 wa/tests/test_diff.py                  | 2 +-
 wa/utils/misc.py                       | 2 +-
 9 files changed, 24 insertions(+), 22 deletions(-)

diff --git a/wa/commands/record.py b/wa/commands/record.py
index e45e8ff4..be72cd09 100644
--- a/wa/commands/record.py
+++ b/wa/commands/record.py
@@ -18,9 +18,11 @@ import sys
 
 
 from wa import Command, settings
-from wa.framework.configuration import RunConfiguration
+from wa.framework import pluginloader
+from wa.framework.agenda import Agenda
 from wa.framework.resource import Executable, NO_ONE, ResourceResolver
-from wa.utils.revent import ReventRecorder
+from wa.framework.configuration import RunConfiguration
+from wa.framework.workload import ApkUiautoWorkload
 
 
 class RecordCommand(Command):
diff --git a/wa/framework/configuration/__init__.py b/wa/framework/configuration/__init__.py
index a3593794..697a4811 100644
--- a/wa/framework/configuration/__init__.py
+++ b/wa/framework/configuration/__init__.py
@@ -12,8 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-from wlauto.core.configuration.configuration import (settings,
-                                                     RunConfiguration,
-                                                     JobGenerator,
-                                                     ConfigurationPoint)
-from wlauto.core.configuration.plugin_cache import PluginCache
+from wa.framework.configuration.core import (settings,
+                                             RunConfiguration,
+                                             JobGenerator,
+                                             ConfigurationPoint)
diff --git a/wa/framework/configuration/default.py b/wa/framework/configuration/default.py
index 5145a6b4..ddfec4af 100644
--- a/wa/framework/configuration/default.py
+++ b/wa/framework/configuration/default.py
@@ -1,7 +1,7 @@
-from wlauto.core.configuration.configuration import MetaConfiguration, RunConfiguration
-from wlauto.core.configuration.plugin_cache import PluginCache
-from wlauto.utils.serializer import yaml
-from wlauto.utils.doc import strip_inlined_text
+from wa.framework.configuration.core import MetaConfiguration, RunConfiguration
+from wa.framework.configuration.plugin_cache import PluginCache
+from wa.utils.serializer import yaml
+from wa.utils.doc import strip_inlined_text
 
 DEFAULT_INSTRUMENTS = ['execution_time',
                        'interrupts',
diff --git a/wa/framework/host.py b/wa/framework/host.py
index 33810b93..43b1bf59 100644
--- a/wa/framework/host.py
+++ b/wa/framework/host.py
@@ -1,6 +1,6 @@
 import os
 
-from wlauto.core.configuration import settings
+from wa.framework.configuration.core import settings
 
 def init_user_directory(overwrite_existing=False):  # pylint: disable=R0914
     """
diff --git a/wa/framework/plugin.py b/wa/framework/plugin.py
index 7c8e5dba..c9828839 100644
--- a/wa/framework/plugin.py
+++ b/wa/framework/plugin.py
@@ -26,7 +26,7 @@ from itertools import chain
 from copy import copy
 
 from wa.framework.configuration.core import settings, ConfigurationPoint as Parameter
-from wa.framework.exception import (NotFoundError, PluginLoaderError,
+from wa.framework.exception import (NotFoundError, PluginLoaderError, TargetError,
                                     ValidationError, ConfigError, HostError)
 from wa.utils import log
 from wa.utils.misc import (ensure_directory_exists as _d, walk_modules, load_class, 
@@ -430,7 +430,7 @@ class Plugin(object):
 
             get_module(name, owner, **kwargs)
 
-        and returns an instance of :class:`wlauto.core.plugin.Module`. If the
+        and returns an instance of :class:`wa.core.plugin.Module`. If the
         module with the specified name is not found, the loader must raise an
         appropriate exception.
 
@@ -743,10 +743,10 @@ class PluginLoader(object):
                 self.logger.warning('Got: {}'.format(e))
             else:
                 msg = 'Failed to load {}'
-                raise LoaderError(msg.format(filepath), sys.exc_info())
+                raise PluginLoaderError(msg.format(filepath), sys.exc_info())
         except Exception as e:
             message = 'Problem loading plugins from {}: {}'
-            raise LoaderError(message.format(filepath, e))
+            raise PluginLoaderError(message.format(filepath, e))
 
     def _discover_in_module(self, module):  # NOQA pylint: disable=too-many-branches
         self.logger.debug('Checking module %s', module.__name__)
diff --git a/wa/framework/signal.py b/wa/framework/signal.py
index 20c6a0b2..4f643933 100644
--- a/wa/framework/signal.py
+++ b/wa/framework/signal.py
@@ -231,7 +231,7 @@ def connect(handler, signal, sender=dispatcher.Any, priority=0):
 
                  .. note:: There is nothing that prevents instrumentation from sending their
                            own signals that are not part of the standard set. However the signal
-                           must always be an :class:`wlauto.core.signal.Signal` instance.
+                           must always be an :class:`wa.core.signal.Signal` instance.
 
         :sender: The handler will be invoked only for the signals emitted by this sender. By
                  default, this is set to :class:`louie.dispatcher.Any`, so the handler will
@@ -270,7 +270,7 @@ def disconnect(handler, signal, sender=dispatcher.Any):
 
         :handler: The callback to be disconnected.
         :signal: The signal the handler is to be disconnected form. It will
-                 be an :class:`wlauto.core.signal.Signal` instance.
+                 be an :class:`wa.core.signal.Signal` instance.
         :sender: If specified, the handler will only be disconnected from the signal
                 sent by this sender.
 
@@ -284,7 +284,7 @@ def send(signal, sender=dispatcher.Anonymous, *args, **kwargs):
 
     Paramters:
 
-        :signal: Signal to be sent. This must be an instance of :class:`wlauto.core.signal.Signal`
+        :signal: Signal to be sent. This must be an instance of :class:`wa.core.signal.Signal`
                  or its subclasses.
         :sender: The sender of the signal (typically, this would be ``self``). Some handlers may only
                  be subscribed to signals from a particular sender.
diff --git a/wa/framework/workload.py b/wa/framework/workload.py
index b2f565e3..e548d74d 100644
--- a/wa/framework/workload.py
+++ b/wa/framework/workload.py
@@ -17,7 +17,8 @@ import os
 import time
 
 from wa.framework.plugin import TargetedPlugin
-from wa.framework.resource import ApkFile, JarFile, ReventFile, NO_ONE
+from wa.framework.resource import (ApkFile, JarFile, ReventFile, NO_ONE,
+                                   Executable)
 from wa.framework.exception import WorkloadError
 
 from devlib.utils.android import ApkInfo
diff --git a/wa/tests/test_diff.py b/wa/tests/test_diff.py
index cc1683cc..52f6fbdf 100644
--- a/wa/tests/test_diff.py
+++ b/wa/tests/test_diff.py
@@ -22,7 +22,7 @@ from unittest import TestCase
 
 from nose.tools import assert_equal
 
-from wlauto.instrumentation.misc import _diff_interrupt_files
+from wa.instrumentation.misc import _diff_interrupt_files
 
 
 class InterruptDiffTest(TestCase):
diff --git a/wa/utils/misc.py b/wa/utils/misc.py
index 4a564c90..a41bbd22 100644
--- a/wa/utils/misc.py
+++ b/wa/utils/misc.py
@@ -54,7 +54,7 @@ from devlib.utils.misc import (ABI_MAP, check_output, walk_modules,
 check_output_logger = logging.getLogger('check_output')
 
 
-# Defined here rather than in wlauto.exceptions due to module load dependencies
+# Defined here rather than in wa.exceptions due to module load dependencies
 def diff_tokens(before_token, after_token):
     """
     Creates a diff of two tokens.

From a17e11251ea89578ea9082772482b4f21719f8e3 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Tue, 11 Apr 2017 09:34:49 +0100
Subject: [PATCH 02/14] Workload: Corrected doc string

---
 wa/framework/workload.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/wa/framework/workload.py b/wa/framework/workload.py
index e548d74d..d03cdddd 100644
--- a/wa/framework/workload.py
+++ b/wa/framework/workload.py
@@ -65,7 +65,7 @@ class Workload(TargetedPlugin):
     def run(self, context):
         """
         Execute the workload. This is the method that performs the actual
-        "work" of the.
+        "work" of the workload.
         """
         pass
 

From b7ed59edcfd1e57a77f186634671699e2b9d2d81 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Wed, 12 Apr 2017 14:47:34 +0100
Subject: [PATCH 03/14] Execution: Corrected spelling

---
 wa/framework/execution.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/wa/framework/execution.py b/wa/framework/execution.py
index d9fc5132..ca89b800 100644
--- a/wa/framework/execution.py
+++ b/wa/framework/execution.py
@@ -209,7 +209,7 @@ class Executor(object):
     """
     The ``Executor``'s job is to set up the execution context and pass to a
     ``Runner`` along with a loaded run specification. Once the ``Runner`` has
-    done its thing, the ``Executor`` performs some final reporint before
+    done its thing, the ``Executor`` performs some final reporting before
     returning.
 
     The initial context set up involves combining configuration from various

From cceecebfa7cb4b4f298d222a47ed081e906215f1 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Tue, 18 Apr 2017 14:40:26 +0100
Subject: [PATCH 04/14] Getters: Renamed import from `__base_filepath` to
 `_base_filepath`

Previously trying to use the `__base_filepath` import from inside the `Package`
class, resulted in the error "`_Package__base_filepath` is not defined."
---
 wa/framework/getters.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/wa/framework/getters.py b/wa/framework/getters.py
index 651ca5a2..7c9986c6 100644
--- a/wa/framework/getters.py
+++ b/wa/framework/getters.py
@@ -31,7 +31,7 @@ import requests
 
 from devlib.utils.android import ApkInfo
 
-from wa import Parameter, settings, __file__ as __base_filepath
+from wa import Parameter, settings, __file__ as _base_filepath
 from wa.framework.resource import ResourceGetter, SourcePriority, NO_ONE 
 from wa.framework.exception import ResourceError
 from wa.utils.misc import (ensure_directory_exists as _d, 
@@ -96,7 +96,7 @@ class Package(ResourceGetter):
 
     def get(self, resource):
         if resource.owner == NO_ONE:
-            basepath = os.path.join(os.path.dirname(__base_filepath), 'assets')
+            basepath = os.path.join(os.path.dirname(_base_filepath), 'assets')
         else:
             modname = resource.owner.__module__
             basepath  = os.path.dirname(sys.modules[modname].__file__)

From 2406d016729be9cf5c15167151772200622d169b Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Wed, 12 Apr 2017 11:39:46 +0100
Subject: [PATCH 05/14] Exec Control: Copied to WA3 and now uses default
 environment.

Moved execution decorators from wlauto to wa.
Modified to use a default environment if none is explicitly specified.
---
 wa/tests/test_exec_control.py | 269 ++++++++++++++++++++++++++++++++++
 wa/utils/exec_control.py      | 110 ++++++++++++++
 2 files changed, 379 insertions(+)
 create mode 100644 wa/tests/test_exec_control.py
 create mode 100644 wa/utils/exec_control.py

diff --git a/wa/tests/test_exec_control.py b/wa/tests/test_exec_control.py
new file mode 100644
index 00000000..490239d2
--- /dev/null
+++ b/wa/tests/test_exec_control.py
@@ -0,0 +1,269 @@
+#    Copyright 2013-2015 ARM Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+# pylint: disable=W0231,W0613,E0611,W0603,R0201
+from unittest import TestCase
+
+from nose.tools import assert_equal, assert_raises
+
+from wlauto.utils.exec_control import (init_environment, reset_environment,
+                                              activate_environment, once,
+                                              once_per_class, once_per_instance)
+
+class TestClass(object):
+
+    def __init__(self):
+        self.count = 0
+
+    @once
+    def initilize_once(self):
+        self.count += 1
+
+    @once_per_class
+    def initilize_once_per_class(self):
+        self.count += 1
+
+    @once_per_instance
+    def initilize_once_per_instance(self):
+        self.count += 1
+
+
+class SubClass(TestClass):
+
+    def __init__(self):
+        super(SubClass, self).__init__()
+
+
+class SubSubClass(SubClass):
+
+    def __init__(self):
+        super(SubSubClass, self).__init__()
+
+
+class AnotherClass(object):
+
+    def __init__(self):
+        self.count = 0
+
+    @once
+    def initilize_once(self):
+        self.count += 1
+
+    @once_per_class
+    def initilize_once_per_class(self):
+        self.count += 1
+
+    @once_per_instance
+    def initilize_once_per_instance(self):
+        self.count += 1
+
+
+class EnvironmentManagementTest(TestCase):
+
+    def test_duplicate_environment(self):
+        init_environment('ENVIRONMENT')
+        assert_raises(ValueError, init_environment, 'ENVIRONMENT')
+
+    def test_reset_missing_environment(self):
+        assert_raises(ValueError, reset_environment, 'MISSING')
+
+    def test_reset_current_environment(self):
+        activate_environment('CURRENT_ENVIRONMENT')
+        t1 = TestClass()
+        t1.initilize_once()
+        assert_equal(t1.count, 1)
+
+        reset_environment()
+        t1.initilize_once()
+        assert_equal(t1.count, 2)
+
+    def test_switch_environment(self):
+        activate_environment('ENVIRONMENT1')
+        t1 = TestClass()
+        t1.initilize_once()
+        assert_equal(t1.count, 1)
+
+        activate_environment('ENVIRONMENT2')
+        t1.initilize_once()
+        assert_equal(t1.count, 2)
+
+        activate_environment('ENVIRONMENT1')
+        t1.initilize_once()
+        assert_equal(t1.count, 2)
+
+    def test_reset_environment_name(self):
+        activate_environment('ENVIRONMENT')
+        t1 = TestClass()
+        t1.initilize_once()
+        assert_equal(t1.count, 1)
+
+        reset_environment('ENVIRONMENT')
+        t1.initilize_once()
+        assert_equal(t1.count, 2)
+
+
+class OnlyOnceEnvironmentTest(TestCase):
+
+    def setUp(self):
+        activate_environment('TEST_ENVIRONMENT')
+
+    def tearDown(self):
+        reset_environment('TEST_ENVIRONMENT')
+
+    def test_single_instance(self):
+        t1 = TestClass()
+        ac = AnotherClass()
+
+        t1.initilize_once()
+        assert_equal(t1.count, 1)
+
+        t1.initilize_once()
+        assert_equal(t1.count, 1)
+
+        ac.initilize_once()
+        assert_equal(ac.count, 1)
+
+
+    def test_mulitple_instances(self):
+        t1 = TestClass()
+        t2 = TestClass()
+
+        t1.initilize_once()
+        assert_equal(t1.count, 1)
+
+        t2.initilize_once()
+        assert_equal(t2.count, 0)
+
+
+    def test_sub_classes(self):
+        t1 = TestClass()
+        sc = SubClass()
+        ss = SubSubClass()
+
+        t1.initilize_once()
+        assert_equal(t1.count, 1)
+
+        sc.initilize_once()
+        sc.initilize_once()
+        assert_equal(sc.count, 0)
+
+        ss.initilize_once()
+        ss.initilize_once()
+        assert_equal(ss.count, 0)
+
+
+class OncePerClassEnvironmentTest(TestCase):
+
+    def setUp(self):
+        activate_environment('TEST_ENVIRONMENT')
+
+    def tearDown(self):
+        reset_environment('TEST_ENVIRONMENT')
+
+    def test_single_instance(self):
+        t1 = TestClass()
+        ac = AnotherClass()
+
+        t1.initilize_once_per_class()
+        assert_equal(t1.count, 1)
+
+        t1.initilize_once_per_class()
+        assert_equal(t1.count, 1)
+
+        ac.initilize_once_per_class()
+        assert_equal(ac.count, 1)
+
+
+    def test_mulitple_instances(self):
+        t1 = TestClass()
+        t2 = TestClass()
+
+        t1.initilize_once_per_class()
+        assert_equal(t1.count, 1)
+
+        t2.initilize_once_per_class()
+        assert_equal(t2.count, 0)
+
+
+    def test_sub_classes(self):
+        t1 = TestClass()
+        sc1 = SubClass()
+        sc2 = SubClass()
+        ss1 = SubSubClass()
+        ss2 = SubSubClass()
+
+        t1.initilize_once_per_class()
+        assert_equal(t1.count, 1)
+
+        sc1.initilize_once_per_class()
+        sc2.initilize_once_per_class()
+        assert_equal(sc1.count, 1)
+        assert_equal(sc2.count, 0)
+
+        ss1.initilize_once_per_class()
+        ss2.initilize_once_per_class()
+        assert_equal(ss1.count, 1)
+        assert_equal(ss2.count, 0)
+
+
+class OncePerInstanceEnvironmentTest(TestCase):
+
+    def setUp(self):
+        activate_environment('TEST_ENVIRONMENT')
+
+    def tearDown(self):
+        reset_environment('TEST_ENVIRONMENT')
+
+    def test_single_instance(self):
+        t1 = TestClass()
+        ac = AnotherClass()
+
+        t1.initilize_once_per_instance()
+        assert_equal(t1.count, 1)
+
+        t1.initilize_once_per_instance()
+        assert_equal(t1.count, 1)
+
+        ac.initilize_once_per_instance()
+        assert_equal(ac.count, 1)
+
+
+    def test_mulitple_instances(self):
+        t1 = TestClass()
+        t2 = TestClass()
+
+        t1.initilize_once_per_instance()
+        assert_equal(t1.count, 1)
+
+        t2.initilize_once_per_instance()
+        assert_equal(t2.count, 1)
+
+
+    def test_sub_classes(self):
+        t1 = TestClass()
+        sc = SubClass()
+        ss = SubSubClass()
+
+        t1.initilize_once_per_instance()
+        assert_equal(t1.count, 1)
+
+        sc.initilize_once_per_instance()
+        sc.initilize_once_per_instance()
+        assert_equal(sc.count, 1)
+
+        ss.initilize_once_per_instance()
+        ss.initilize_once_per_instance()
+        assert_equal(ss.count, 1)
diff --git a/wa/utils/exec_control.py b/wa/utils/exec_control.py
new file mode 100644
index 00000000..29261937
--- /dev/null
+++ b/wa/utils/exec_control.py
@@ -0,0 +1,110 @@
+from inspect import getmro
+
+# "environment" management:
+__environments = {}
+__active_environment = None
+
+
+def activate_environment(name):
+    """
+    Sets the current tracking environment to ``name``. If an
+    environment with that name does not already exist, it will be
+    created.
+    """
+    #pylint: disable=W0603
+    global __active_environment
+
+    if name not in __environments.keys():
+        init_environment(name)
+    __active_environment = name
+
+def init_environment(name):
+    """
+    Create a new environment called ``name``, but do not set it as the
+    current environment.
+
+    :raises: ``ValueError`` if an environment with name ``name``
+             already exists.
+    """
+    if name in __environments.keys():
+        msg = "Environment {} already exists".format(name)
+        raise ValueError(msg)
+    __environments[name] = []
+
+def reset_environment(name=None):
+    """
+    Reset method call tracking for environment ``name``. If ``name`` is
+    not specified or is ``None``, reset the current active environment.
+
+    :raises: ``ValueError`` if an environment with name ``name``
+          does not exist.
+    """
+
+    if name is not None:
+        if name not in __environments.keys():
+            msg = "Environment {} does not exist".format(name)
+            raise ValueError(msg)
+        __environments[name] = []
+    else:
+        if __active_environment is None:
+            activate_environment('default')
+        __environments[__active_environment] = []
+
+# The decorators:
+def once_per_instance(method):
+    """
+    The specified method will be invoked only once for every bound
+    instance within the environment.
+    """
+    def wrapper(*args, **kwargs):
+        if __active_environment is None:
+            activate_environment('default')
+        func_id = repr(args[0])
+        if func_id in __environments[__active_environment]:
+            return
+        else:
+            __environments[__active_environment].append(func_id)
+        return method(*args, **kwargs)
+
+    return wrapper
+
+def once_per_class(method):
+    """
+    The specified method will be invoked only once for all instances
+    of a class within the environment.
+    """
+    def wrapper(*args, **kwargs):
+        if __active_environment is None:
+            activate_environment('default')
+
+        func_id = repr(method.func_name) + repr(args[0].__class__)
+
+        if func_id in __environments[__active_environment]:
+            return
+        else:
+            __environments[__active_environment].append(func_id)
+        return method(*args, **kwargs)
+
+    return wrapper
+
+def once(method):
+    """
+    The specified method will be invoked only once within the
+    environment.
+    """
+    def wrapper(*args, **kwargs):
+        if __active_environment is None:
+            activate_environment('default')
+
+        func_id = repr(method.func_name)
+        # Store the least derived class, which isn't object, to account
+        # for subclasses.
+        func_id += repr(getmro(args[0].__class__)[-2])
+
+        if func_id in __environments[__active_environment]:
+            return
+        else:
+            __environments[__active_environment].append(func_id)
+        return method(*args, **kwargs)
+
+    return wrapper

From 7815df59d4323bb648cfdcfb8f35f191b368ba74 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Wed, 12 Apr 2017 12:05:23 +0100
Subject: [PATCH 06/14] Getters: Added support for finding revent files.

Revent files are automatically placed in the sub folder `revent_files` in the
workload directory when recording, therefore when trying to retrieve recordings
the getter now looks inside of the sub directory.
---
 wa/framework/getters.py | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/wa/framework/getters.py b/wa/framework/getters.py
index 7c9986c6..777a2620 100644
--- a/wa/framework/getters.py
+++ b/wa/framework/getters.py
@@ -80,7 +80,16 @@ def get_from_location(basepath, resource):
         path = os.path.join(basepath, 'bin', resource.abi, resource.filename)
         if os.path.exists(path):
             return path
-    elif resource.kind in ['apk', 'jar', 'revent']:
+    elif resource.kind == 'revent':
+        path = os.path.join(basepath, 'revent_files')
+        if os.path.exists(path):
+            files = get_by_extension(path, resource.kind)
+            found_resource = get_generic_resource(resource, files)
+            if found_resource:
+                return found_resource
+        files = get_by_extension(basepath, resource.kind)
+        return get_generic_resource(resource, files)
+    elif resource.kind in ['apk', 'jar']:
         files = get_by_extension(basepath, resource.kind)
         return get_generic_resource(resource, files)
 

From 6f0d18f92183b948741abf6375927d0ad0ad06d8 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Wed, 12 Apr 2017 14:04:28 +0100
Subject: [PATCH 07/14] Executor: Renamed `device_manage` to
 `self.target_manager`

---
 wa/framework/execution.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/wa/framework/execution.py b/wa/framework/execution.py
index ca89b800..29095a5a 100644
--- a/wa/framework/execution.py
+++ b/wa/framework/execution.py
@@ -225,7 +225,7 @@ class Executor(object):
         self.error_logged = False
         self.warning_logged = False
         pluginloader = None
-        self.device_manager = None
+        self.target_manager = None
         self.device = None
 
     def execute(self, config_manager, output):
@@ -249,12 +249,12 @@ class Executor(object):
         output.write_config(config)
 
         self.logger.info('Connecting to target')
-        target_manager = TargetManager(config.run_config.device,
+        self.target_manager = TargetManager(config.run_config.device,
                                        config.run_config.device_config)
-        output.write_target_info(target_manager.get_target_info())
+        output.write_target_info(self.target_manager.get_target_info())
 
         self.logger.info('Initializing execution conetext')
-        context = ExecutionContext(config_manager, target_manager, output)
+        context = ExecutionContext(config_manager, self.target_manager, output)
 
         self.logger.info('Generating jobs')
         config_manager.generate_jobs(context)
@@ -262,7 +262,7 @@ class Executor(object):
         output.write_state()
 
         self.logger.info('Installing instrumentation')
-        for instrument in config_manager.get_instruments(target_manager.target):
+        for instrument in config_manager.get_instruments(self.target_manager.target):
             instrumentation.install(instrument)
         instrumentation.validate()
 

From 9308855f14d6289c682c782485e986ae0ce2a71b Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Wed, 12 Apr 2017 14:18:25 +0100
Subject: [PATCH 08/14] TargetDescriptor: Now adds parameters with default
 values.

Previously if a parameter was not specified via config it would not
be used during initialisation even if the parameter had a default
value. Now any parameters with default values are populated as necessary.
---
 wa/framework/target/descriptor.py | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/wa/framework/target/descriptor.py b/wa/framework/target/descriptor.py
index 9e01ebac..8515556f 100644
--- a/wa/framework/target/descriptor.py
+++ b/wa/framework/target/descriptor.py
@@ -33,6 +33,11 @@ def instantiate_target(tdesc, params, connect=None):
 
     tp, pp, cp = {}, {}, {}
 
+    for supported_params, new_params in (target_params, tp), (platform_params, pp), (conn_params, cp):
+        for name, value in supported_params.iteritems():
+            if value.default:
+                new_params[name] = value.default
+
     for name, value in params.iteritems():
         if name in target_params:
             tp[name] = value
@@ -62,6 +67,8 @@ def instantiate_assistant(tdesc, params, target):
     for param in tdesc.assistant_params:
         if param.name in params:
             assistant_params[param.name] = params[param.name]
+        elif param.default:
+            assistant_params[param.name] = param.default
     return tdesc.assistant(target, **assistant_params)
 
 

From adaa83b6ebcb7dfb421a9c2efdd269bb0fee4837 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Wed, 12 Apr 2017 17:01:01 +0100
Subject: [PATCH 09/14] ReventUtils: Added ReventRecorder

Added `ReventRecorder` which is used to deal with the revent binary
on the device including deloyment, running commands and cleaning up
again.
---
 wa/utils/revent.py | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/wa/utils/revent.py b/wa/utils/revent.py
index b7de2750..3d192881 100644
--- a/wa/utils/revent.py
+++ b/wa/utils/revent.py
@@ -16,9 +16,11 @@
 from __future__ import division
 import os
 import struct
+import signal
 from datetime import datetime
 from collections import namedtuple
 
+from wa.framework.resource import Executable, NO_ONE, ResourceResolver
 
 GENERAL_MODE = 0
 GAMEPAD_MODE = 1
@@ -249,3 +251,38 @@ class ReventRecording(object):
 
     def __del__(self):
         self.close()
+
+
+def get_revent_binary(abi):
+    resolver = ResourceResolver()
+    resolver.load()
+    resource = Executable(NO_ONE, abi, 'revent')
+    return resolver.get(resource)
+
+
+class ReventRecorder(object):
+
+    def __init__(self, target):
+        self.target = target
+        self.executable = self.target.get_installed('revent')
+
+    def deploy(self):
+        if not self.executable:
+            host_executable = get_revent_binary(self.target.abi)
+            self.executable = self.target.install(host_executable)
+
+    def remove(self):
+        if self.executable:
+            self.target.uninstall('revent')
+
+    def start_record(self, revent_file):
+        command = '{} record -s {}'.format(self.executable, revent_file)
+        self.target.kick_off(command, self.target.is_rooted)
+
+    def stop_record(self):
+        self.target.killall('revent', signal.SIGINT, as_root=self.target.is_rooted)
+
+    def replay(self, revent_file, timeout=None):
+        self.target.killall('revent')
+        command = "{} replay {}".format(self.executable, revent_file)
+        self.target.execute(command, timeout=timeout)

From 4b84a68038e3bc517f18ade69f5b97d9657afe37 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Wed, 12 Apr 2017 16:24:43 +0100
Subject: [PATCH 10/14] Workload: Added ReventWorkload and updated ReventGUI

Added a new ReventWorkload as a base for revent based workloads.
Updated ReventGui to use `revent_recorder` and now uses `target.model`
instead of `taget.name`.
---
 wa/__init__.py           |   2 +-
 wa/framework/workload.py | 155 ++++++++++++++++++++++++++++-----------
 2 files changed, 114 insertions(+), 43 deletions(-)

diff --git a/wa/__init__.py b/wa/__init__.py
index d005ef27..0eb95c57 100644
--- a/wa/__init__.py
+++ b/wa/__init__.py
@@ -14,4 +14,4 @@ from wa.framework.plugin import Plugin, Parameter
 from wa.framework.processor import ResultProcessor
 from wa.framework.resource import (NO_ONE, JarFile, ApkFile, ReventFile, File,
                                    Executable)
-from wa.framework.workload import Workload, ApkUiautoWorkload
+from wa.framework.workload import Workload, ApkUiautoWorkload, ReventWorkload
diff --git a/wa/framework/workload.py b/wa/framework/workload.py
index d03cdddd..4b9302a7 100644
--- a/wa/framework/workload.py
+++ b/wa/framework/workload.py
@@ -18,8 +18,10 @@ import time
 
 from wa.framework.plugin import TargetedPlugin
 from wa.framework.resource import (ApkFile, JarFile, ReventFile, NO_ONE,
-                                   Executable)
+                                   Executable, File)
 from wa.framework.exception import WorkloadError
+from wa.utils.revent import ReventRecorder
+from wa.utils.exec_control import once
 
 from devlib.utils.android import ApkInfo
 
@@ -94,25 +96,29 @@ class Workload(TargetedPlugin):
         return '<Workload {}>'.format(self.name)
 
 
-class ApkUiautoWorkload(Workload):
-    
-    platform = 'android'
+class ApkUIWorkload(Workload):
+
+    # May be optionally overwritten by subclasses
+    # Times are in seconds
+    loading_time = 10
 
     def __init__(self, target, **kwargs):
-        super(ApkUiautoWorkload, self).__init__(target, **kwargs)
-        self.apk = ApkHander(self)
-        self.gui = UiAutomatorGUI(self)
+        super(ApkUIWorkload, self).__init__(target, **kwargs)
+        self.apk = None
+        self.gui = None
 
     def init_resources(self, context):
         self.apk.init_resources(context.resolver)
         self.gui.init_resources(context.resolver)
         self.gui.init_commands()
 
+    @once
     def initialize(self, context):
         self.gui.deploy()
 
     def setup(self, context):
         self.apk.setup(context)
+        time.sleep(self.loading_time)
         self.gui.setup()
 
     def run(self, context):
@@ -125,10 +131,40 @@ class ApkUiautoWorkload(Workload):
         self.gui.teardown()
         self.apk.teardown()
 
+    @once
     def finalize(self, context):
         self.gui.remove()
 
 
+class ApkUiautoWorkload(ApkUIWorkload):
+
+    platform = 'android'
+
+    def __init__(self, target, **kwargs):
+        super(ApkUiautoWorkload, self).__init__(target, **kwargs)
+        self.apk = ApkHander(self)
+        self.gui = UiAutomatorGUI(self)
+
+
+class ReventWorkload(ApkUIWorkload):
+
+    # May be optionally overwritten by subclasses
+    # Times are in seconds
+    setup_timeout = 5 * 60
+    run_timeout = 10 * 60
+    extract_results_timeout = 5 * 60
+    teardown_timeout = 5 * 60
+
+    def __init__(self, target, **kwargs):
+        super(ReventWorkload, self).__init__(target, **kwargs)
+        self.apk = ApkHander(self)
+        self.gui = ReventGUI(self, target,
+                             self.setup_timeout,
+                             self.run_timeout,
+                             self.extract_results_timeout,
+                             self.teardown_timeout)
+
+
 class UiAutomatorGUI(object):
 
     stages = ['setup', 'runWorkload', 'extractResults', 'teardown']
@@ -209,57 +245,92 @@ class UiAutomatorGUI(object):
 
 class ReventGUI(object):
 
-    def __init__(self, workload, target, setup_timeout=5 * 60, run_timeout=10 * 60):
+    def __init__(self, workload, target, setup_timeout, run_timeout,
+                 extract_results_timeout, teardown_timeout):
         self.workload = workload
         self.target = target
         self.setup_timeout = setup_timeout
         self.run_timeout = run_timeout
+        self.extract_results_timeout = extract_results_timeout
+        self.teardown_timeout = teardown_timeout
+        self.revent_recorder = ReventRecorder(self.target)
         self.on_target_revent_binary = self.target.get_workpath('revent')
-        self.on_target_setup_revent = self.target.get_workpath('{}.setup.revent'.format(self.target.name))
-        self.on_target_run_revent = self.target.get_workpath('{}.run.revent'.format(self.target.name))
+        self.on_target_setup_revent = self.target.get_workpath('{}.setup.revent'.format(self.target.model))
+        self.on_target_run_revent = self.target.get_workpath('{}.run.revent'.format(self.target.model))
+        self.on_target_extract_results_revent = self.target.get_workpath('{}.extract_results.revent'.format(self.target.model))
+        self.on_target_teardown_revent = self.target.get_workpath('{}.teardown.revent'.format(self.target.model))
         self.logger = logging.getLogger('revent')
         self.revent_setup_file = None
         self.revent_run_file = None
+        self.revent_extract_results_file = None
+        self.revent_teardown_file = None
 
-    def init_resources(self, context):
-        self.revent_setup_file = context.resolver.get(ReventFile(self.workload, 'setup'))
-        self.revent_run_file = context.resolver.get(ReventFile(self.workload, 'run'))
+    def init_resources(self, resolver):
+        self.revent_setup_file = resolver.get(ReventFile(owner=self.workload,
+                                                         stage='setup',
+                                                         target=self.target.model),
+                                              strict=False)
+        self.revent_run_file = resolver.get(ReventFile(owner=self.workload,
+                                                       stage='run',
+                                                       target=self.target.model))
+        self.revent_extract_results_file = resolver.get(ReventFile(owner=self.workload,
+                                                                 stage='extract_results',
+                                                                 target=self.target.model),
+                                                        strict=False)
+        self.revent_teardown_file = resolver.get(resource=ReventFile(owner=self.workload,
+                                                            stage='teardown',
+                                                            target=self.target.model),
+                                                strict=False)
 
-    def setup(self, context):
-        self._check_revent_files(context)
-        self.target.killall('revent')
-        command = '{} replay {}'.format(self.on_target_revent_binary, self.on_target_setup_revent)
-        self.target.execute(command, timeout=self.setup_timeout)
+    def deploy(self):
+        self.revent_recorder.deploy()
 
-    def run(self, context):
-        command = '{} replay {}'.format(self.on_target_revent_binary, self.on_target_run_revent)
-        self.logger.debug('Replaying {}'.format(os.path.basename(self.on_target_run_revent)))
-        self.target.execute(command, timeout=self.run_timeout)
+    def setup(self):
+        self._check_revent_files()
+        self.revent_recorder.replay(self.on_target_setup_revent,
+                                    timeout=self.setup_timeout)
+
+    def run(self):
+        msg = 'Replaying {}'
+        self.logger.debug(msg.format(os.path.basename(self.on_target_run_revent)))
+        self.revent_recorder.replay(self.on_target_run_revent,
+                                    timeout=self.run_timeout)
         self.logger.debug('Replay completed.')
 
-    def teardown(self, context):
+    def extract_results(self):
+        if self.revent_extract_results_file:
+            self.revent_recorder.replay(self.on_target_extract_results_revent,
+                                        timeout=self.extract_results_timeout)
+
+    def teardown(self):
+        if self.revent_teardown_file:
+            self.revent_recorder.replay(self.on_target_teardown_revent,
+                                        timeout=self.teardown_timeout)
         self.target.remove(self.on_target_setup_revent)
         self.target.remove(self.on_target_run_revent)
+        self.target.remove(self.on_target_extract_results_revent)
+        self.target.remove(self.on_target_teardown_revent)
 
-    def _check_revent_files(self, context):
-        # check the revent binary
-        revent_binary = context.resolver.get(Executable(NO_ONE, self.target.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 target, {0}'
-            raise WorkloadError(message.format(self.target.name))
+    def remove(self):
+        self.revent_recorder.remove()
+
+    def init_commands(self):
+        pass
+
+    def _check_revent_files(self):
         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 target, {0}'
-            raise WorkloadError(message.format(self.target.name))
+            message = '{0}.run.revent file does not exist, ' \
+                      'Please provide one for your target, {0}'
+            raise WorkloadError(message.format(self.target.model))
 
-        self.on_target_revent_binary = self.target.install(revent_binary)
         self.target.push(self.revent_run_file, self.on_target_run_revent)
-        self.target.push(self.revent_setup_file, self.on_target_setup_revent)
+        if self.revent_setup_file:
+            self.target.push(self.revent_setup_file, self.on_target_setup_revent)
+        if self.revent_extract_results_file:
+            self.target.push(self.revent_extract_results_file, self.on_target_extract_results_revent)
+        if self.revent_teardown_file:
+            self.target.push(self.revent_teardown_file, self.on_target_teardown_revent)
 
 
 class ApkHander(object):
@@ -281,10 +352,10 @@ class ApkHander(object):
         self.logcat_log = None
 
     def init_resources(self, resolver):
-        self.apk_file = resolver.get(ApkFile(self.owner, 
+        self.apk_file = resolver.get(ApkFile(self.owner,
                                              variant=self.variant,
-                                             version=self.version), 
-                                     strict=self.strict)
+                                             version=self.version),
+                                             strict=self.strict)
         self.apk_info = ApkInfo(self.apk_file)
 
     def setup(self, context):
@@ -333,7 +404,7 @@ class ApkHander(object):
 
     def start_activity(self):
         cmd = 'am start -W -n {}/{}'
-        output = self.target.execute(cmd.format(self.apk_info.package, 
+        output = self.target.execute(cmd.format(self.apk_info.package,
                                                 self.apk_info.activity))
         if 'Error:' in output:
             # this will dismiss any error dialogs

From 3ad0c67c634ff0381783a2a2f9992760964bd320 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Wed, 12 Apr 2017 14:08:13 +0100
Subject: [PATCH 11/14] AngrybirdsRio: Added revent workload

---
 wa/workloads/angrybirds_rio/__init__.py | 27 +++++++++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 wa/workloads/angrybirds_rio/__init__.py

diff --git a/wa/workloads/angrybirds_rio/__init__.py b/wa/workloads/angrybirds_rio/__init__.py
new file mode 100644
index 00000000..b56c66b3
--- /dev/null
+++ b/wa/workloads/angrybirds_rio/__init__.py
@@ -0,0 +1,27 @@
+#    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.
+#
+
+
+from wa import ReventWorkload
+
+
+class AngryBirdsRio(ReventWorkload):
+
+    name = 'angrybirds_rio'
+    description = """
+    Angry Birds Rio game.
+
+    The sequel to the very popular Android 2D game.
+    """

From 311ac1b80372fef1db22eb80f1281b764f2d6f1d Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Wed, 12 Apr 2017 17:33:58 +0100
Subject: [PATCH 12/14] Record Command: Updated record command

Updated the record command to allow revent recordings to be made.
They can be performed in 3 ways; the default is a standard recording
as in WA2, it can start a specified package before starting the
recording or a workload can be specified which launches the workload
and prompts the user to record each stage, naming and storing the recordings
appropriately.
---
 wa/commands/record.py | 188 ++++++++++++++++++++++++++++++++++--------
 1 file changed, 155 insertions(+), 33 deletions(-)

diff --git a/wa/commands/record.py b/wa/commands/record.py
index be72cd09..830dee5d 100644
--- a/wa/commands/record.py
+++ b/wa/commands/record.py
@@ -15,23 +15,24 @@
 
 import os
 import sys
+from time import sleep
 
-
-from wa import Command, settings
+from wa import Command
 from wa.framework import pluginloader
-from wa.framework.agenda import Agenda
-from wa.framework.resource import Executable, NO_ONE, ResourceResolver
-from wa.framework.configuration import RunConfiguration
-from wa.framework.workload import ApkUiautoWorkload
+from wa.framework.resource import ResourceResolver
+from wa.framework.target.manager import TargetManager
+from wa.utils.revent import ReventRecorder
 
 
 class RecordCommand(Command):
 
     name = 'record'
-    description = '''Performs a revent recording
+    description = '''
+    Performs a revent recording
 
     This command helps making revent recordings. It will automatically
-    deploy revent and even has the option of automatically opening apps.
+    deploy revent and has options to automatically open apps and record
+    specified stages of a workload.
 
     Revent allows you to record raw inputs such as screen swipes or button presses.
     This can be useful for recording inputs for workloads such as games that don't
@@ -45,10 +46,18 @@ class RecordCommand(Command):
        it can be automatically determined. On Android device it will be obtained
        from ``build.prop``, on Linux devices it is obtained from ``/proc/device-tree/model``.
      - suffix is used by WA to determine which part of the app execution the
-       recording is for, currently these are either ``setup`` or ``run``. This
-       should be specified with the ``-s`` argument.
+       recording is for, currently these are either ``setup``, ``run``, ``extract_results``
+       or ``teardown``. All stages except ``run`` are optional and these should
+       be specified with the ``-s``, ``-e`` or ``-t`` arguments respectively,
+       or optionally ``-a`` to indicate all stages should be recorded.
     '''
 
+    def __init__(self, **kwargs):
+        super(RecordCommand, self).__init__(**kwargs)
+        self.tm = None
+        self.target = None
+        self.revent_recorder = None
+
     def initialize(self, context):
         self.parser.add_argument('-d', '--device', metavar='DEVICE',
                                  help='''
@@ -56,39 +65,152 @@ class RecordCommand(Command):
                                  take precedence over the device (if any)
                                  specified in configuration.
                                  ''')
+        self.parser.add_argument('-o', '--output', help='Specify the output file', metavar='FILE')
+        self.parser.add_argument('-s', '--setup', help='Record a recording for setup stage',
+                                 action='store_true')
+        self.parser.add_argument('-e', '--extract_results', help='Record a recording for extract_results stage',
+                                 action='store_true')
+        self.parser.add_argument('-t', '--teardown', help='Record a recording for teardown stage',
+                                 action='store_true')
+        self.parser.add_argument('-a', '--all', help='Record recordings for available stages',
+                                 action='store_true')
 
-    def execute(state, args):
+        # Need validation
+        self.parser.add_argument('-C', '--clear', help='Clear app cache before launching it',
+                                 action='store_true')
+        group = self.parser.add_mutually_exclusive_group(required=False)
+        group.add_argument('-p', '--package', help='Package to launch before recording')
+        group.add_argument('-w', '--workload', help='Name of a revent workload (mostly games)')
+
+    def validate_args(self, args):
+        if args.clear and not (args.package or args.workload):
+            self.logger.error("Package/Workload must be specified if you want to clear cache")
+            sys.exit()
+        if args.workload and args.output:
+            self.logger.error("Output file cannot be specified with Workload")
+            sys.exit()
+        if not args.workload and (args.setup or args.extract_results or
+                                  args.teardown or args.all):
+            self.logger.error("Cannot specify a recording stage without a Workload")
+            sys.exit()
+
+    def execute(self, state, args):
+        self.validate_args(args)
         if args.device:
-            device =  args.device
+            device = args.device
             device_config = {}
         else:
             device = state.run_config.device
-            device_config = state.run_config.device_config
-        target_manager = TargetManager(device, device_config)
+            device_config = state.run_config.device_config or {}
 
+        self.tm = TargetManager(device, device_config)
+        self.target = self.tm.target
+        self.revent_recorder = ReventRecorder(self.target)
+        self.revent_recorder.deploy()
 
-def get_revent_binary(abi):
-    resolver = ResourceResolver()
-    resource = Executable(NO_ONE, abi, 'revent')
-    return resolver.get(resource)
+        if args.workload:
+            self.workload_record(args)
+        elif args.package:
+            self.package_record(args)
+        else:
+            self.manual_record(args)
 
+        self.revent_recorder.remove()
 
-class ReventRecorder(object):
+    def record(self, revent_file, name, output_path):
+        msg = 'Press Enter when you are ready to record {}...'
+        self.logger.info(msg.format(name))
+        raw_input('')
+        self.revent_recorder.start_record(revent_file)
+        msg = 'Press Enter when you have finished recording {}...'
+        self.logger.info(msg.format(name))
+        raw_input('')
+        self.revent_recorder.stop_record()
 
-    def __init__(self, target):
-        self.target = target
-        self.executable = None
-        self.deploy()
+        if not os.path.isdir(output_path):
+            os.makedirs(output_path)
 
-    def deploy(self):
-        host_executable = get_revent_binary(self.target.abi)
-        self.executable = self.target.install(host_executable)
+        revent_file_name = self.target.path.basename(revent_file)
+        host_path = os.path.join(output_path, revent_file_name)
+        if os.path.exists(host_path):
+            msg = 'Revent file \'{}\' already exists, overwrite? [y/n]'
+            self.logger.info(msg.format(revent_file_name))
+            if raw_input('') == 'y':
+                os.remove(host_path)
+            else:
+                msg = 'Did not pull and overwrite \'{}\''
+                self.logger.warning(msg.format(revent_file_name))
+                return
+        msg = 'Pulling \'{}\' from device'
+        self.logger.info(msg.format(self.target.path.basename(revent_file)))
+        self.target.pull(revent_file, output_path, as_root=self.target.is_rooted)
 
-    def record(self, path):
-        name = os.path.basename(path)
-        target_path = self.target.get_workpath(name)
-        command = '{} record {}'
+    def manual_record(self, args):
+        output_path, file_name = self._split_revent_location(args.output)
+        revent_file = self.target.get_workpath(file_name)
+        self.record(revent_file, '', output_path)
+        msg = 'Recording is available at: \'{}\''
+        self.logger.info(msg.format(os.path.join(output_path, file_name)))
 
-    def remove(self):
-        if self.executable:
-            self.target.uninstall('revent')
+    def package_record(self, args):
+        if args.clear:
+            self.target.execute('pm clear {}'.format(args.package))
+        self.logger.info('Starting {}'.format(args.package))
+        cmd = 'monkey -p {} -c android.intent.category.LAUNCHER 1'
+        self.target.execute(cmd.format(args.package))
+
+        output_path, file_name = self._split_revent_location(args.output)
+        revent_file = self.target.get_workpath(file_name)
+        self.record(revent_file, '', output_path)
+        msg = 'Recording is available at: \'{}\''
+        self.logger.info(msg.format(os.path.join(output_path, file_name)))
+
+    def workload_record(self, args):
+        context = LightContext(self.tm)
+        setup_revent = '{}.setup.revent'.format(self.target.model)
+        run_revent = '{}.run.revent'.format(self.target.model)
+        extract_results_revent = '{}.extract_results.revent'.format(self.target.model)
+        teardown_file_revent = '{}.teardown.revent'.format(self.target.model)
+        setup_file = self.target.get_workpath(setup_revent)
+        run_file = self.target.get_workpath(run_revent)
+        extract_results_file = self.target.get_workpath(extract_results_revent)
+        teardown_file = self.target.get_workpath(teardown_file_revent)
+
+        self.logger.info('Deploying {}'.format(args.workload))
+        workload = pluginloader.get_workload(args.workload, self.target)
+        workload.apk.init_resources(context.resolver)
+        workload.apk.setup(context)
+        sleep(workload.loading_time)
+
+        output_path = os.path.join(workload.dependencies_directory,
+                                   'revent_files')
+        if args.setup or args.all:
+            self.record(setup_file, 'SETUP', output_path)
+        self.record(run_file, 'RUN', output_path)
+        if args.extract_results or args.all:
+            self.record(extract_results_file, 'EXTRACT_RESULTS', output_path)
+        if args.teardown or args.all:
+            self.record(teardown_file, 'TEARDOWN', output_path)
+        self.logger.info('Tearing down {}'.format(args.workload))
+        workload.teardown(context)
+        self.logger.info('Recording(s) are available at: \'{}\''.format(output_path))
+
+    def _split_revent_location(self, output):
+        output_path = None
+        file_name = None
+        if output:
+            output_path, file_name, = os.path.split(output)
+
+        if not file_name:
+            file_name = '{}.revent'.format(self.target.model)
+        if not output_path:
+            output_path = os.getcwdu()
+
+        return output_path, file_name
+
+# Used to satisfy the workload API
+class LightContext(object):
+    def __init__(self, tm):
+        self.tm = tm
+        self.resolver = ResourceResolver()
+        self.resolver.load()

From 16f2bc69f0bfcc36edd17b2c8f084a5aaefecaf6 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Wed, 12 Apr 2017 17:40:24 +0100
Subject: [PATCH 13/14] Replay Command: Added a replay command

The replay command can be used to replay an revent recording on a device.
---
 wa/commands/record.py | 52 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/wa/commands/record.py b/wa/commands/record.py
index 830dee5d..44639794 100644
--- a/wa/commands/record.py
+++ b/wa/commands/record.py
@@ -208,6 +208,58 @@ class RecordCommand(Command):
 
         return output_path, file_name
 
+class ReplayCommand(Command):
+
+    name = 'replay'
+    description = '''
+    Replay a revent recording
+
+    Revent allows you to record raw inputs such as screen swipes or button presses.
+    See ``wa show record`` to see how to make an revent recording.
+    '''
+
+    def initialize(self, context):
+        self.parser.add_argument('recording', help='The name of the file to replay',
+                                 metavar='FILE')
+        self.parser.add_argument('-d', '--device', help='The name of the device')
+        self.parser.add_argument('-p', '--package', help='Package to launch before recording')
+        self.parser.add_argument('-C', '--clear', help='Clear app cache before launching it',
+                                 action="store_true")
+
+    # pylint: disable=W0201
+    def execute(self, state, args):
+        if args.device:
+            device = args.device
+            device_config = {}
+        else:
+            device = state.run_config.device
+            device_config = state.run_config.device_config or {}
+
+        target_manager = TargetManager(device, device_config)
+        self.target = target_manager.target
+        revent_file = self.target.path.join(self.target.working_directory,
+                                            os.path.split(args.revent)[1])
+
+        self.logger.info("Pushing file to target")
+        self.target.push(args.revent, self.target.working_directory)
+
+        revent_recorder = ReventRecorder(target_manager.target)
+        revent_recorder.deploy()
+
+        if args.clear:
+            self.target.execute('pm clear {}'.format(args.package))
+
+        if args.package:
+            self.logger.info('Starting {}'.format(args.package))
+            cmd = 'monkey -p {} -c android.intent.category.LAUNCHER 1'
+            self.target.execute(cmd.format(args.package))
+
+        self.logger.info("Starting replay")
+        revent_recorder.replay(revent_file)
+        self.logger.info("Finished replay")
+        revent_recorder.remove()
+
+
 # Used to satisfy the workload API
 class LightContext(object):
     def __init__(self, tm):

From 16a0e84469ae2e5785a11cc7f6f65a5350d7abe1 Mon Sep 17 00:00:00 2001
From: Marc Bonnici <marc.bonnici@arm.com>
Date: Tue, 18 Apr 2017 12:00:00 +0100
Subject: [PATCH 14/14] Runner: Now finalizes all completed workloads at the
 end of a run.

Previously when finalizing a run the workloads themselves were not included.
This ensures that each completed workloads finalize method is called.
---
 wa/framework/execution.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/wa/framework/execution.py b/wa/framework/execution.py
index 29095a5a..9d4eb8f9 100644
--- a/wa/framework/execution.py
+++ b/wa/framework/execution.py
@@ -361,6 +361,10 @@ class Runner(object):
         self.pm.process_run_output(self.context)
         self.pm.export_run_output(self.context)
         self.pm.finalize()
+        log.indent()
+        for job in self.context.completed_jobs:
+            job.finalize(self.context)
+        log.dedent()
 
     def run_next_job(self, context):
         job = context.start_job()