mirror of
				https://github.com/ARM-software/workload-automation.git
				synced 2025-11-04 09:02:12 +00:00 
			
		
		
		
	@@ -1,7 +1,10 @@
 | 
			
		||||
.. _revent_files_creation:
 | 
			
		||||
 | 
			
		||||
revent
 | 
			
		||||
======
 | 
			
		||||
++++++
 | 
			
		||||
 | 
			
		||||
Overview and Usage
 | 
			
		||||
==================
 | 
			
		||||
 | 
			
		||||
revent utility can be used to record and later play back a sequence of user
 | 
			
		||||
input events, such as key presses and touch screen taps. This is an alternative
 | 
			
		||||
@@ -17,36 +20,47 @@ to Android UI Automator for providing automation for workloads. ::
 | 
			
		||||
                        info:shows info about each event char device
 | 
			
		||||
                        any additional parameters make it verbose
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. note:: There are now also WA commands that perform the below steps.
 | 
			
		||||
          Please see ``wa show record/replay`` and ``wa record/replay --help``
 | 
			
		||||
          for details.
 | 
			
		||||
 | 
			
		||||
Recording
 | 
			
		||||
---------
 | 
			
		||||
 | 
			
		||||
To record, transfer the revent binary to the device, then invoke ``revent
 | 
			
		||||
record``, giving it the time (in seconds) you want to record for, and the
 | 
			
		||||
file you want to record to (WA expects these files to have .revent
 | 
			
		||||
plugin)::
 | 
			
		||||
WA features a ``record`` command that will automatically deploy and start
 | 
			
		||||
revent on the target device::
 | 
			
		||||
 | 
			
		||||
        host$  adb push revent /data/local/revent
 | 
			
		||||
        host$  adb shell
 | 
			
		||||
        device#  cd /data/local
 | 
			
		||||
        device#  ./revent record 1000 my_recording.revent
 | 
			
		||||
    wa record
 | 
			
		||||
    INFO     Connecting to device...
 | 
			
		||||
    INFO     Press Enter when you are ready to record...
 | 
			
		||||
    [Pressed Enter]
 | 
			
		||||
    INFO     Press Enter when you have finished recording...
 | 
			
		||||
    [Pressed Enter]
 | 
			
		||||
    INFO     Pulling files from device
 | 
			
		||||
 | 
			
		||||
Once started, you will need to get the target device ready to record (e.g.
 | 
			
		||||
unlock screen, navigate menus and launch an app) then press ``ENTER``.
 | 
			
		||||
The recording has now started and button presses, taps, etc you perform on
 | 
			
		||||
the device will go into the .revent file. To stop the recording simply press
 | 
			
		||||
``ENTER`` again.
 | 
			
		||||
 | 
			
		||||
Once you have finished recording the revent file will be pulled from the device
 | 
			
		||||
to the current directory. It will be named ``{device_model}.revent``. When
 | 
			
		||||
recording revent files for a ``GameWorkload`` you can use the ``-s`` option to
 | 
			
		||||
add ``run`` or ``setup`` suffixes.
 | 
			
		||||
 | 
			
		||||
From version 2.6 of WA onwards, a "gamepad" recording mode is also supported.
 | 
			
		||||
This mode requires a gamepad to be connected to the device when recoridng, but
 | 
			
		||||
the recordings produced in this mode should be portable across devices.
 | 
			
		||||
 | 
			
		||||
For more information run please read :ref:`record-command`
 | 
			
		||||
 | 
			
		||||
The recording has now started and button presses, taps, etc you perform on the
 | 
			
		||||
device will go into the .revent file. The recording will stop after the
 | 
			
		||||
specified time period, and you can also stop it by hitting return in the adb
 | 
			
		||||
shell.
 | 
			
		||||
 | 
			
		||||
Replaying
 | 
			
		||||
---------
 | 
			
		||||
 | 
			
		||||
To replay a recorded file, run ``revent replay`` on the device, giving it the
 | 
			
		||||
file you want to replay::
 | 
			
		||||
To replay a recorded file, run ``wa replay``, giving it the file you want to
 | 
			
		||||
replay::
 | 
			
		||||
 | 
			
		||||
        device#  ./revent replay my_recording.revent
 | 
			
		||||
        wa replay my_recording.revent
 | 
			
		||||
 | 
			
		||||
For more information run please read :ref:`replay-command`
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Using revent With Workloads
 | 
			
		||||
@@ -100,3 +114,396 @@ where as UI Automator only works for Android UI elements (such as text boxes or
 | 
			
		||||
radio buttons), which makes the latter useless for things like games. Recording
 | 
			
		||||
revent sequence is also faster than writing automation code (on the other hand,
 | 
			
		||||
one would need maintain a different revent log for each screen resolution).
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Using state detection with revent
 | 
			
		||||
=================================
 | 
			
		||||
 | 
			
		||||
State detection can be used to verify that a workload is executing as expected.
 | 
			
		||||
This utility, if enabled, and if state definitions are available for the
 | 
			
		||||
particular workload, takes a screenshot after the setup and the run revent
 | 
			
		||||
sequence, matches the screenshot to a state and compares with the expected
 | 
			
		||||
state. A WorkloadError is raised if an unexpected state is encountered.
 | 
			
		||||
 | 
			
		||||
To enable state detection, make sure a valid state definition file and
 | 
			
		||||
templates exist for your workload and set the check_states parameter to True.
 | 
			
		||||
 | 
			
		||||
State definition directory
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
State and phase definitions should be placed in a directory of the following
 | 
			
		||||
structure inside the dependencies directory of each workload (along with
 | 
			
		||||
revent files etc):
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
   dependencies/
 | 
			
		||||
      <workload_name>/
 | 
			
		||||
         state_definitions/
 | 
			
		||||
            definition.yaml
 | 
			
		||||
            templates/
 | 
			
		||||
               <oneTemplate>.png
 | 
			
		||||
               <anotherTemplate>.png
 | 
			
		||||
               ...
 | 
			
		||||
 | 
			
		||||
definition.yaml file
 | 
			
		||||
--------------------
 | 
			
		||||
 | 
			
		||||
This defines each state of the workload and lists which templates are expected
 | 
			
		||||
to be found and how many are required to be detected for a conclusive match. It
 | 
			
		||||
also defines the expected state in each workload phase where a state detection
 | 
			
		||||
is run (currently those are setup_complete and run_complete).
 | 
			
		||||
 | 
			
		||||
Templates are picture elements to be matched in a screenshot. Each template
 | 
			
		||||
mentioned in the definition file should be placed as a file with the same name
 | 
			
		||||
and a .png extension inside the templates folder. Creating template png files
 | 
			
		||||
is as simple as taking a screenshot of the workload in a given state, cropping
 | 
			
		||||
out the relevant templates (eg. a button, label or other unique element that is
 | 
			
		||||
present in that state) and storing them in PNG format.
 | 
			
		||||
 | 
			
		||||
Please see the definition file for Angry Birds below as an example to
 | 
			
		||||
understand the format. Note that more than just two states (for the afterSetup
 | 
			
		||||
and afterRun phase) can be defined and this helps track the cause of errors in
 | 
			
		||||
case an unexpected state is encountered.
 | 
			
		||||
 | 
			
		||||
.. code-block:: yaml
 | 
			
		||||
 | 
			
		||||
    workload_name: angrybirds
 | 
			
		||||
 | 
			
		||||
    workload_states:
 | 
			
		||||
      - state_name: titleScreen
 | 
			
		||||
        templates:
 | 
			
		||||
          - play_button
 | 
			
		||||
          - logo
 | 
			
		||||
        matches: 2
 | 
			
		||||
      - state_name: worldSelection
 | 
			
		||||
        templates:
 | 
			
		||||
          - first_world_thumb
 | 
			
		||||
          - second_world_thumb
 | 
			
		||||
          - third_world_thumb
 | 
			
		||||
          - fourth_world_thumb
 | 
			
		||||
        matches: 3
 | 
			
		||||
      - state_name: level_selection
 | 
			
		||||
        templates:
 | 
			
		||||
          - locked_level
 | 
			
		||||
          - first_level
 | 
			
		||||
        matches: 2
 | 
			
		||||
      - state_name: gameplay
 | 
			
		||||
        templates:
 | 
			
		||||
          - pause_button
 | 
			
		||||
          - score_label_text
 | 
			
		||||
        matches: 2
 | 
			
		||||
      - state_name: pause_screen
 | 
			
		||||
        templates:
 | 
			
		||||
          - replay_button
 | 
			
		||||
          - menu_button
 | 
			
		||||
          - resume_button
 | 
			
		||||
          - help_button
 | 
			
		||||
        matches: 4
 | 
			
		||||
      - state_name: level_cleared_screen
 | 
			
		||||
        templates:
 | 
			
		||||
          - level_cleared_text
 | 
			
		||||
          - menu_button
 | 
			
		||||
          - replay_button
 | 
			
		||||
          - fast_forward_button
 | 
			
		||||
        matches: 4
 | 
			
		||||
 | 
			
		||||
    workload_phases:
 | 
			
		||||
      - phase_name: setup_complete
 | 
			
		||||
        expected_state: gameplay
 | 
			
		||||
      - phase_name: run_complete
 | 
			
		||||
        expected_state: level_cleared_screen
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
File format of revent recordings
 | 
			
		||||
================================
 | 
			
		||||
 | 
			
		||||
You do not need to understand recording format in order to use revent. This
 | 
			
		||||
section is intended for those looking to extend revent in some way, or to
 | 
			
		||||
utilize revent recordings for other purposes.
 | 
			
		||||
 | 
			
		||||
Format Overview
 | 
			
		||||
---------------
 | 
			
		||||
 | 
			
		||||
Recordings are stored in a binary format. A recording consists of three
 | 
			
		||||
sections::
 | 
			
		||||
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |       Header        |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                     |
 | 
			
		||||
    |  Device Description |
 | 
			
		||||
    |                     |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                     |
 | 
			
		||||
    |                     |
 | 
			
		||||
    |     Event Stream    |
 | 
			
		||||
    |                     |
 | 
			
		||||
    |                     |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
 | 
			
		||||
The header contains metadata describing the recording. The device description
 | 
			
		||||
contains information about input devices involved in this recording. Finally,
 | 
			
		||||
the event stream contains the recorded input events.
 | 
			
		||||
 | 
			
		||||
All fields are either fixed size or prefixed with their length or the number of
 | 
			
		||||
(fixed-sized) elements.
 | 
			
		||||
 | 
			
		||||
.. note:: All values below are little endian
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Recording Header
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
An revent recoding header has the following structure
 | 
			
		||||
 | 
			
		||||
 * It starts with the "magic" string ``REVENT`` to indicate that this is an
 | 
			
		||||
   revent recording.
 | 
			
		||||
 * The magic is followed by a 16 bit version number. This indicates the format
 | 
			
		||||
   version of the recording that follows. Current version is ``2``.
 | 
			
		||||
 * The next 16 bits indicate the type of the recording. This dictates the
 | 
			
		||||
   structure of the Device Description section. Valid values are:
 | 
			
		||||
 | 
			
		||||
        ``0``
 | 
			
		||||
                This is a general input event recording. The device description
 | 
			
		||||
                contains a list of paths from which the events where recorded.
 | 
			
		||||
        ``1``
 | 
			
		||||
                This a gamepad recording. The device description contains the
 | 
			
		||||
                description of the gamepad used to create the recording.
 | 
			
		||||
 | 
			
		||||
 * The header is zero-padded to 128 bits.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
     0                   1                   2                   3
 | 
			
		||||
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |      'R'      |      'E'      |      'V'      |      'E'      |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |      'N'      |      'T'      |            Version            |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |             Mode              |            PADDING            |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                            PADDING                            |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Device Description
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
This section describes the input devices used in the recording. Its structure is
 | 
			
		||||
determined by the value of ``Mode`` field in the header.
 | 
			
		||||
 | 
			
		||||
general recording
 | 
			
		||||
~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
.. note:: This is the only format supported prior to version ``2``.
 | 
			
		||||
 | 
			
		||||
The recording has been made from all available input devices. This section
 | 
			
		||||
contains the list of ``/dev/input`` paths for the devices, prefixed with total
 | 
			
		||||
number of the devices recorded.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
     0                   1                   2                   3
 | 
			
		||||
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                       Number of devices                       |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |             Device paths              +-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                                       |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Similarly, each device path is a length-prefixed string. Unlike C strings, the
 | 
			
		||||
path is *not* NULL-terminated.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
     0                   1                   2                   3
 | 
			
		||||
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                     Length of device path                     |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                          Device path                          |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
gamepad recording
 | 
			
		||||
~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
The recording has been made from a specific gamepad. All events in the stream
 | 
			
		||||
will be for that device only. The section describes the device properties that
 | 
			
		||||
will be used to create a virtual input device using ``/dev/uinput``. Please
 | 
			
		||||
see ``linux/input.h`` header in the Linux kernel source for more information
 | 
			
		||||
about the fields in this section.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
     0                   1                   2                   3
 | 
			
		||||
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |            bustype            |             vendor            |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |            product            |            version            |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                         name_length                           |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                             name                              |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                            ev_bits                            |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                       key_bits (96 bytes)                     |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                       rel_bits (96 bytes)                     |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                       abs_bits (96 bytes)                     |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                          num_absinfo                          |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                        absinfo entries                        |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Each ``absinfo`` entry consists of six 32 bit values. The number of entries is
 | 
			
		||||
determined by the ``abs_bits`` field.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
     0                   1                   2                   3
 | 
			
		||||
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                            value                              |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                           minimum                             |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                           maximum                             |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                             fuzz                              |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                             flat                              |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                          resolution                           |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Event stream
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
The majority of an revent recording will be made up of the input events that were
 | 
			
		||||
recorded. The event stream is prefixed with the number of events in the stream,
 | 
			
		||||
and start and end times for the recording.
 | 
			
		||||
 | 
			
		||||
     0                   1                   2                   3
 | 
			
		||||
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                        Number of events                       |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                  Number of events (cont.)                     |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                      Start Time Seconds                       |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                  Start Time Seconds (cont.)                   |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                    Start Time Microseconds                    |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |              Start Time Microseconds (cont.)                  |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                        End Time Seconds                       |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                    End Time Seconds (cont.)                   |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                      End Time Microseconds                    |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                End Time Microseconds (cont.)                  |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |             Events                                            |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                                               |
 | 
			
		||||
    |                                       +-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                                       |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Event structure
 | 
			
		||||
~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
Each event entry structured as follows:
 | 
			
		||||
 | 
			
		||||
 * An unsigned integer representing which device from the list of device paths
 | 
			
		||||
   this event is for (zero indexed). E.g. Device ID = 3 would be the 4th
 | 
			
		||||
   device in the list of device paths.
 | 
			
		||||
 * A signed integer representing the number of seconds since "epoch" when the
 | 
			
		||||
   event was recorded.
 | 
			
		||||
 * A signed integer representing the microseconds part of the timestamp.
 | 
			
		||||
 * An unsigned integer representing the event type
 | 
			
		||||
 * An unsigned integer representing the event code
 | 
			
		||||
 * An unsigned integer representing the event value
 | 
			
		||||
 | 
			
		||||
For more information about the event type, code and value please read:
 | 
			
		||||
https://www.kernel.org/doc/Documentation/input/event-codes.txt
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
     0                   1                   2                   3
 | 
			
		||||
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |           Device ID           |        Timestamp Seconds      |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |                       Timestamp Seconds (cont.)               |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |   Timestamp Seconds (cont.)   |        stamp Micoseconds      |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |              Timestamp Micoseconds (cont.)                    |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    | Timestamp Micoseconds (cont.) |          Event Type           |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |          Event Code           |          Event Value          |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
    |       Event Value (cont.)     |
 | 
			
		||||
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Parser
 | 
			
		||||
------
 | 
			
		||||
 | 
			
		||||
WA has a parser for revent recordings. This can be used to work with revent
 | 
			
		||||
recordings in scripts. Here is an example:
 | 
			
		||||
 | 
			
		||||
.. code:: python
 | 
			
		||||
 | 
			
		||||
    from wlauto.utils.revent import ReventRecording
 | 
			
		||||
 | 
			
		||||
    with ReventRecording('/path/to/recording.revent') as recording:
 | 
			
		||||
        print "Recording: {}".format(recording.filepath)
 | 
			
		||||
        print "There are {} input events".format(recording.num_events)
 | 
			
		||||
        print "Over a total of {} seconds".format(recording.duration)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								wa/assets/bin/arm64/revent
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								wa/assets/bin/arm64/revent
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								wa/assets/bin/armeabi/revent
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								wa/assets/bin/armeabi/revent
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -178,7 +178,7 @@ class RecordCommand(Command):
 | 
			
		||||
 | 
			
		||||
        self.logger.info('Deploying {}'.format(args.workload))
 | 
			
		||||
        workload = pluginloader.get_workload(args.workload, self.target)
 | 
			
		||||
        workload.apk.init_resources(context.resolver)
 | 
			
		||||
        workload.apk.initialize(context)
 | 
			
		||||
        workload.apk.setup(context)
 | 
			
		||||
        sleep(workload.loading_time)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
from copy import copy
 | 
			
		||||
from copy import copy, deepcopy
 | 
			
		||||
from collections import OrderedDict, defaultdict
 | 
			
		||||
 | 
			
		||||
from wa.framework.exception import ConfigError, NotFoundError
 | 
			
		||||
@@ -34,7 +34,7 @@ KIND_MAP = {
 | 
			
		||||
 | 
			
		||||
Status = enum(['UNKNOWN', 'NEW', 'PENDING',
 | 
			
		||||
               'STARTED', 'CONNECTED', 'INITIALIZED', 'RUNNING',
 | 
			
		||||
               'SKIPPED', 'ABORTED', 'FAILED', 'PARTIAL', 'OK'])
 | 
			
		||||
               'OK', 'PARTIAL', 'FAILED', 'ABORTED', 'SKIPPED'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -272,12 +272,12 @@ class ConfigurationPoint(object):
 | 
			
		||||
            value = merge_config_values(getattr(obj, self.name), value)
 | 
			
		||||
        setattr(obj, self.name, value)
 | 
			
		||||
 | 
			
		||||
    def validate(self, obj):
 | 
			
		||||
    def validate(self, obj, check_mandatory=True):
 | 
			
		||||
        value = getattr(obj, self.name, None)
 | 
			
		||||
        if value is not None:
 | 
			
		||||
            self.validate_value(obj.name, value)
 | 
			
		||||
        else:
 | 
			
		||||
            if self.mandatory:
 | 
			
		||||
            if check_mandatory and self.mandatory:
 | 
			
		||||
                msg = 'No value specified for mandatory parameter "{}" in {}.'
 | 
			
		||||
                raise ConfigError(msg.format(self.name, obj.name))
 | 
			
		||||
 | 
			
		||||
@@ -928,7 +928,8 @@ class JobSpec(Configuration):
 | 
			
		||||
    def merge_workload_parameters(self, plugin_cache):
 | 
			
		||||
        # merge global generic and specific config
 | 
			
		||||
        workload_params = plugin_cache.get_plugin_config(self.workload_name,
 | 
			
		||||
                                                         generic_name="workload_parameters")
 | 
			
		||||
                                                         generic_name="workload_parameters",
 | 
			
		||||
                                                         is_final=False)
 | 
			
		||||
 | 
			
		||||
        cfg_points = plugin_cache.get_plugin_parameters(self.workload_name)
 | 
			
		||||
        for source in self._sources:
 | 
			
		||||
@@ -1041,7 +1042,7 @@ class JobGenerator(object):
 | 
			
		||||
                sections.insert(0, ancestor)
 | 
			
		||||
 | 
			
		||||
            for workload_entry in workload_entries:
 | 
			
		||||
                job_spec = create_job_spec(workload_entry, sections,
 | 
			
		||||
                job_spec = create_job_spec(deepcopy(workload_entry), sections,
 | 
			
		||||
                                           target_manager, self.plugin_cache,
 | 
			
		||||
                                           self.disabled_instruments)
 | 
			
		||||
                if self.ids_to_run:
 | 
			
		||||
 
 | 
			
		||||
@@ -99,7 +99,7 @@ class PluginCache(object):
 | 
			
		||||
    def list_plugins(self, kind=None):
 | 
			
		||||
        return self.loader.list_plugins(kind)
 | 
			
		||||
 | 
			
		||||
    def get_plugin_config(self, plugin_name, generic_name=None):
 | 
			
		||||
    def get_plugin_config(self, plugin_name, generic_name=None, is_final=True):
 | 
			
		||||
        config = obj_dict(not_in_dict=['name'])
 | 
			
		||||
        config.name = plugin_name
 | 
			
		||||
 | 
			
		||||
@@ -120,7 +120,8 @@ class PluginCache(object):
 | 
			
		||||
        else:
 | 
			
		||||
            # A more complicated merge that involves priority of sources and
 | 
			
		||||
            # specificity
 | 
			
		||||
            self._merge_using_priority_specificity(plugin_name, generic_name, config)
 | 
			
		||||
            self._merge_using_priority_specificity(plugin_name, generic_name,
 | 
			
		||||
                                                   config, is_final)
 | 
			
		||||
 | 
			
		||||
        return config
 | 
			
		||||
 | 
			
		||||
@@ -152,13 +153,13 @@ class PluginCache(object):
 | 
			
		||||
 | 
			
		||||
    def _get_target_params(self, name):
 | 
			
		||||
        td = self.targets[name]
 | 
			
		||||
        params = {p.name: p for p in chain(td.target_params, td.platform_params)}
 | 
			
		||||
        params = {p.name: p for p in chain(td.target_params, td.platform_params, td.conn_params)}
 | 
			
		||||
        #params['connection_settings'] = {p.name: p for p in td.conn_params}
 | 
			
		||||
        return params
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=too-many-nested-blocks, too-many-branches
 | 
			
		||||
    def _merge_using_priority_specificity(self, specific_name, 
 | 
			
		||||
                                          generic_name, final_config):
 | 
			
		||||
                                          generic_name, merged_config, is_final=True):
 | 
			
		||||
        """
 | 
			
		||||
        WA configuration can come from various sources of increasing priority,
 | 
			
		||||
        as well as being specified in a generic and specific manner (e.g
 | 
			
		||||
@@ -175,13 +176,15 @@ class PluginCache(object):
 | 
			
		||||
        In this situation it is not possible to know the end users intention
 | 
			
		||||
        and WA will error.
 | 
			
		||||
 | 
			
		||||
        :param generic_name: The name of the generic configuration
 | 
			
		||||
                             e.g ``device_config``
 | 
			
		||||
        :param specific_name: The name of the specific configuration used
 | 
			
		||||
                              e.g ``nexus10``
 | 
			
		||||
        :param cfg_point: A dict of ``ConfigurationPoint``s to be used when
 | 
			
		||||
                          merging configuration.  keys=config point name, 
 | 
			
		||||
                          values=config point
 | 
			
		||||
        :param generic_name: The name of the generic configuration
 | 
			
		||||
                             e.g ``device_config``
 | 
			
		||||
        :param merge_config: A dict of ``ConfigurationPoint``s to be used when
 | 
			
		||||
                             merging configuration.  keys=config point name, 
 | 
			
		||||
                             values=config point
 | 
			
		||||
        :param is_final: if ``True`` (the default) make sure that mandatory
 | 
			
		||||
                         parameters are set.
 | 
			
		||||
 | 
			
		||||
        :rtype: A fully merged and validated configuration in the form of a
 | 
			
		||||
                obj_dict.
 | 
			
		||||
@@ -197,18 +200,18 @@ class PluginCache(object):
 | 
			
		||||
        # set_value uses the 'name' attribute of the passed object in it error
 | 
			
		||||
        # messages, to ensure these messages make sense the name will have to be
 | 
			
		||||
        # changed several times during this function.
 | 
			
		||||
        final_config.name = specific_name
 | 
			
		||||
        merged_config.name = specific_name
 | 
			
		||||
 | 
			
		||||
        for source in sources:
 | 
			
		||||
            try:
 | 
			
		||||
                update_config_from_source(final_config, source, ms)
 | 
			
		||||
                update_config_from_source(merged_config, source, ms)
 | 
			
		||||
            except ConfigError as e:
 | 
			
		||||
                raise ConfigError('Error in "{}":\n\t{}'.format(source, str(e)))
 | 
			
		||||
 | 
			
		||||
        # Validate final configuration
 | 
			
		||||
        final_config.name = specific_name
 | 
			
		||||
        merged_config.name = specific_name
 | 
			
		||||
        for cfg_point in ms.cfg_points.itervalues():
 | 
			
		||||
            cfg_point.validate(final_config)
 | 
			
		||||
            cfg_point.validate(merged_config, check_mandatory=is_final)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MergeState(object):
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ from wa.framework.configuration.core import settings, Status
 | 
			
		||||
from wa.framework.exception import (WAError, ConfigError, TimeoutError,
 | 
			
		||||
                                    InstrumentError, TargetError, HostError,
 | 
			
		||||
                                    TargetNotRespondingError)
 | 
			
		||||
from wa.framework.job import Job
 | 
			
		||||
from wa.framework.output import init_job_output
 | 
			
		||||
from wa.framework.plugin import Artifact
 | 
			
		||||
from wa.framework.processor import ProcessorManager
 | 
			
		||||
@@ -75,6 +76,11 @@ class ExecutionContext(object):
 | 
			
		||||
            return True
 | 
			
		||||
        return self.current_job.spec.id != self.next_job.spec.id
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def workload(self):
 | 
			
		||||
        if self.current_job:
 | 
			
		||||
            return self.current_job.workload
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def job_output(self):
 | 
			
		||||
        if self.current_job:
 | 
			
		||||
@@ -150,6 +156,11 @@ class ExecutionContext(object):
 | 
			
		||||
        self.output.write_result()
 | 
			
		||||
        self.current_job = None
 | 
			
		||||
 | 
			
		||||
    def set_status(self, status, force=False):
 | 
			
		||||
        if not self.current_job:
 | 
			
		||||
            raise RuntimeError('No jobs in progress')
 | 
			
		||||
        self.current_job.set_status(status, force)
 | 
			
		||||
 | 
			
		||||
    def extract_results(self):
 | 
			
		||||
        self.tm.extract_results(self)
 | 
			
		||||
 | 
			
		||||
@@ -171,6 +182,14 @@ class ExecutionContext(object):
 | 
			
		||||
    def write_state(self):
 | 
			
		||||
        self.run_output.write_state()
 | 
			
		||||
 | 
			
		||||
    def get_metric(self, name):
 | 
			
		||||
        try:
 | 
			
		||||
            return self.output.get_metric(name)
 | 
			
		||||
        except HostError:
 | 
			
		||||
            if not self.current_job:
 | 
			
		||||
                raise
 | 
			
		||||
            return self.run_output.get_metric(name)
 | 
			
		||||
 | 
			
		||||
    def add_metric(self, name, value, units=None, lower_is_better=False,
 | 
			
		||||
                   classifiers=None):
 | 
			
		||||
        if self.current_job:
 | 
			
		||||
@@ -373,12 +392,12 @@ class Runner(object):
 | 
			
		||||
        try:
 | 
			
		||||
            log.indent()
 | 
			
		||||
            self.do_run_job(job, context)
 | 
			
		||||
            job.status = Status.OK
 | 
			
		||||
            job.set_status(Status.OK)
 | 
			
		||||
        except KeyboardInterrupt:
 | 
			
		||||
            job.status = Status.ABORTED
 | 
			
		||||
            job.set_status(Status.ABORTED)
 | 
			
		||||
            raise
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            job.status = Status.FAILED
 | 
			
		||||
            job.set_status(Status.FAILED)
 | 
			
		||||
            context.add_event(e.message)
 | 
			
		||||
            if not getattr(e, 'logged', None):
 | 
			
		||||
                log.log_error(e, self.logger)
 | 
			
		||||
@@ -392,7 +411,7 @@ class Runner(object):
 | 
			
		||||
            self.check_job(job)
 | 
			
		||||
 | 
			
		||||
    def do_run_job(self, job, context):
 | 
			
		||||
        job.status = Status.RUNNING
 | 
			
		||||
        job.set_status(Status.RUNNING)
 | 
			
		||||
        self.send(signal.JOB_STARTED)
 | 
			
		||||
 | 
			
		||||
        with signal.wrap('JOB_TARGET_CONFIG', self):
 | 
			
		||||
@@ -411,15 +430,15 @@ class Runner(object):
 | 
			
		||||
                self.pm.process_job_output(context)
 | 
			
		||||
                self.pm.export_job_output(context)
 | 
			
		||||
            except Exception:
 | 
			
		||||
                job.status = Status.PARTIAL
 | 
			
		||||
                job.set_status(Status.PARTIAL)
 | 
			
		||||
                raise
 | 
			
		||||
 | 
			
		||||
        except KeyboardInterrupt:
 | 
			
		||||
            job.status = Status.ABORTED
 | 
			
		||||
            job.set_status(Status.ABORTED)
 | 
			
		||||
            self.logger.info('Got CTRL-C. Aborting.')
 | 
			
		||||
            raise
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            job.status = Status.FAILED
 | 
			
		||||
            job.set_status(Status.FAILED)
 | 
			
		||||
            if not getattr(e, 'logged', None):
 | 
			
		||||
                log.log_error(e, self.logger)
 | 
			
		||||
                e.logged = True
 | 
			
		||||
@@ -436,19 +455,24 @@ class Runner(object):
 | 
			
		||||
            if job.retries < rc.max_retries:
 | 
			
		||||
                msg = 'Job {} iteration {} completed with status {}. retrying...'
 | 
			
		||||
                self.logger.error(msg.format(job.id, job.status, job.iteration))
 | 
			
		||||
                self.retry_job(job)
 | 
			
		||||
                self.context.move_failed(job)
 | 
			
		||||
                job.retries += 1
 | 
			
		||||
                job.status = Status.PENDING
 | 
			
		||||
                self.context.job_queue.insert(0, job)
 | 
			
		||||
                self.context.write_state()
 | 
			
		||||
            else:
 | 
			
		||||
                msg = 'Job {} iteration {} completed with status {}. '\
 | 
			
		||||
                      'Max retries exceeded.'
 | 
			
		||||
                self.logger.error(msg.format(job.id, job.status, job.iteration))
 | 
			
		||||
                self.logger.error(msg.format(job.id, job.iteration, job.status))
 | 
			
		||||
                self.context.failed_jobs += 1
 | 
			
		||||
        else:  # status not in retry_on_status
 | 
			
		||||
            self.logger.info('Job completed with status {}'.format(job.status))
 | 
			
		||||
            self.context.successful_jobs += 1
 | 
			
		||||
 | 
			
		||||
    def retry_job(self, job):
 | 
			
		||||
        retry_job = Job(job.spec, job.iteration, self.context)
 | 
			
		||||
        retry_job.workload = job.workload
 | 
			
		||||
        retry_job.retries = job.retries + 1
 | 
			
		||||
        retry_job.set_status(Status.PENDING)
 | 
			
		||||
        self.context.job_queue.insert(0, retry_job)
 | 
			
		||||
        
 | 
			
		||||
    def send(self, s):
 | 
			
		||||
        signal.send(s, self, self.context)
 | 
			
		||||
 
 | 
			
		||||
@@ -104,7 +104,8 @@ from collections import OrderedDict
 | 
			
		||||
 | 
			
		||||
from wa.framework import signal
 | 
			
		||||
from wa.framework.plugin import Plugin
 | 
			
		||||
from wa.framework.exception import WAError, TargetNotRespondingError, TimeoutError
 | 
			
		||||
from wa.framework.exception import (WAError, TargetNotRespondingError, TimeoutError,
 | 
			
		||||
                                    WorkloadError)
 | 
			
		||||
from wa.utils.log import log_error
 | 
			
		||||
from wa.utils.misc import get_traceback, isiterable
 | 
			
		||||
from wa.utils.types import identifier, enum, level
 | 
			
		||||
@@ -250,7 +251,7 @@ def check_failures():
 | 
			
		||||
 | 
			
		||||
class ManagedCallback(object):
 | 
			
		||||
    """
 | 
			
		||||
    This wraps instruments' callbacks to ensure that errors do interfer
 | 
			
		||||
    This wraps instruments' callbacks to ensure that errors do not interfer
 | 
			
		||||
    with run execution.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
@@ -270,7 +271,14 @@ class ManagedCallback(object):
 | 
			
		||||
                global failures_detected  # pylint: disable=W0603
 | 
			
		||||
                failures_detected = True
 | 
			
		||||
                log_error(e, logger)
 | 
			
		||||
                disable(self.instrument)
 | 
			
		||||
                context.add_event(e.message)
 | 
			
		||||
                if isinstance(e, WorkloadError):
 | 
			
		||||
                    context.set_status('FAILED')
 | 
			
		||||
                else:
 | 
			
		||||
                    if context.current_job:
 | 
			
		||||
                        context.set_status('PARTIAL')
 | 
			
		||||
                    else:
 | 
			
		||||
                        raise
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Need this to keep track of callbacks, because the dispatcher only keeps
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import logging
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
from wa.framework import pluginloader, signal
 | 
			
		||||
from wa.framework.configuration.core import Status
 | 
			
		||||
@@ -37,6 +38,7 @@ class Job(object):
 | 
			
		||||
        self.context = context
 | 
			
		||||
        self.workload = None
 | 
			
		||||
        self.output = None
 | 
			
		||||
        self.run_time = None
 | 
			
		||||
        self.retries = 0
 | 
			
		||||
        self._status = Status.NEW
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +58,7 @@ class Job(object):
 | 
			
		||||
        self.logger.info('Initializing job {}'.format(self.id))
 | 
			
		||||
        with signal.wrap('WORKLOAD_INITIALIZED', self, context):
 | 
			
		||||
            self.workload.initialize(context)
 | 
			
		||||
        self.status = Status.PENDING
 | 
			
		||||
        self.set_status(Status.PENDING)
 | 
			
		||||
        context.update_job_state(self)
 | 
			
		||||
 | 
			
		||||
    def configure_target(self, context):
 | 
			
		||||
@@ -71,7 +73,11 @@ class Job(object):
 | 
			
		||||
    def run(self, context):
 | 
			
		||||
        self.logger.info('Running job {}'.format(self.id))
 | 
			
		||||
        with signal.wrap('WORKLOAD_EXECUTION', self, context):
 | 
			
		||||
            self.workload.run(context)
 | 
			
		||||
            start_time = datetime.utcnow()
 | 
			
		||||
            try:
 | 
			
		||||
                self.workload.run(context)
 | 
			
		||||
            finally:
 | 
			
		||||
                self.run_time = datetime.utcnow() - start_time
 | 
			
		||||
 | 
			
		||||
    def process_output(self, context):
 | 
			
		||||
        self.logger.info('Processing output for job {}'.format(self.id))
 | 
			
		||||
@@ -90,3 +96,8 @@ class Job(object):
 | 
			
		||||
        self.logger.info('Finalizing job {}'.format(self.id))
 | 
			
		||||
        with signal.wrap('WORKLOAD_FINALIZED', self, context):
 | 
			
		||||
            self.workload.finalize(context)
 | 
			
		||||
 | 
			
		||||
    def set_status(self, status, force=False):
 | 
			
		||||
        status = Status(status)
 | 
			
		||||
        if force or self.status < status:
 | 
			
		||||
            self.status = status
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,18 @@ class Output(object):
 | 
			
		||||
    def status(self, value):
 | 
			
		||||
        self.result.status =  value
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def metrics(self):
 | 
			
		||||
        if self.result is None:
 | 
			
		||||
            return []
 | 
			
		||||
        return self.result.metrics
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def artifacts(self):
 | 
			
		||||
        if self.result is None:
 | 
			
		||||
            return []
 | 
			
		||||
        return self.result.artifacts
 | 
			
		||||
 | 
			
		||||
    def __init__(self, path):
 | 
			
		||||
        self.basepath = path
 | 
			
		||||
        self.result = None
 | 
			
		||||
@@ -84,6 +96,9 @@ class Output(object):
 | 
			
		||||
    def add_event(self, message):
 | 
			
		||||
        self.result.add_event(message)
 | 
			
		||||
 | 
			
		||||
    def get_metric(self, name):
 | 
			
		||||
        return self.result.get_metric(name)
 | 
			
		||||
 | 
			
		||||
    def get_artifact(self, name):
 | 
			
		||||
        return self.result.get_artifact(name)
 | 
			
		||||
 | 
			
		||||
@@ -241,6 +256,12 @@ class Result(object):
 | 
			
		||||
    def add_event(self, message):
 | 
			
		||||
        self.events.append(Event(message))
 | 
			
		||||
 | 
			
		||||
    def get_metric(self, name):
 | 
			
		||||
        for metric in self.metrics:
 | 
			
		||||
            if metric.name == name:
 | 
			
		||||
                return metric
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def get_artifact(self, name):
 | 
			
		||||
        for artifact in self.artifacts:
 | 
			
		||||
            if artifact.name == name:
 | 
			
		||||
 
 | 
			
		||||
@@ -110,9 +110,8 @@ class JobState(object):
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def from_pod(pod):
 | 
			
		||||
        instance = JobState(pod['id'], pod['label'], Status(pod['status']))
 | 
			
		||||
        instance = JobState(pod['id'], pod['label'], pod['iteration'], Status(pod['status']))
 | 
			
		||||
        instance.retries = pod['retries']
 | 
			
		||||
        instance.iteration = pod['iteration']
 | 
			
		||||
        instance.timestamp = pod['timestamp']
 | 
			
		||||
        return instance
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,8 @@ from collections import OrderedDict
 | 
			
		||||
from copy import copy
 | 
			
		||||
 | 
			
		||||
from devlib import (LinuxTarget, AndroidTarget, LocalLinuxTarget,
 | 
			
		||||
                    Platform, Juno, TC2, Gem5SimulationPlatform)
 | 
			
		||||
                    Platform, Juno, TC2, Gem5SimulationPlatform,
 | 
			
		||||
                    AdbConnection, SshConnection, LocalConnection)
 | 
			
		||||
 | 
			
		||||
from wa.framework import pluginloader
 | 
			
		||||
from wa.framework.exception import PluginLoaderError
 | 
			
		||||
@@ -248,16 +249,90 @@ GEM5_PLATFORM_PARAMS = [
 | 
			
		||||
              '''),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# name --> (target_class, params_list, defaults, assistant_class)
 | 
			
		||||
 | 
			
		||||
CONNECTION_PARAMS = {
 | 
			
		||||
    AdbConnection: [
 | 
			
		||||
        Parameter('device', kind=str,
 | 
			
		||||
                description="""
 | 
			
		||||
                ADB device name
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('adb_server', kind=str,
 | 
			
		||||
                description="""
 | 
			
		||||
                ADB server to connect to.
 | 
			
		||||
                """),
 | 
			
		||||
    ],
 | 
			
		||||
    SshConnection: [
 | 
			
		||||
        Parameter('host', kind=str, mandatory=True,
 | 
			
		||||
                description="""
 | 
			
		||||
                Host name or IP address of the target.
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('username', kind=str, mandatory=True,
 | 
			
		||||
                description="""
 | 
			
		||||
                User name to connect with
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('password', kind=str,
 | 
			
		||||
                description="""
 | 
			
		||||
                Password to use.
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('keyfile', kind=str,
 | 
			
		||||
                description="""
 | 
			
		||||
                Key file to use
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('port', kind=int,
 | 
			
		||||
                description="""
 | 
			
		||||
                The port SSH server is listening on on the target.
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('telent', kind=bool, default=False,
 | 
			
		||||
                description="""
 | 
			
		||||
                If set to ``True``, a Telent connection, rather than
 | 
			
		||||
                SSH will be used.
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('password_prompt', kind=str,
 | 
			
		||||
                description="""
 | 
			
		||||
                Password prompt to expect
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('original_prompt', kind=str,
 | 
			
		||||
                description="""
 | 
			
		||||
                Original shell prompt to expect.
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('sudo_cmd', kind=str,
 | 
			
		||||
                default="sudo -- sh -c '{}'",
 | 
			
		||||
                description="""
 | 
			
		||||
                Sudo command to use. Must have ``"{}"``` specified
 | 
			
		||||
                somewher in the string it indicate where the command
 | 
			
		||||
                to be run via sudo is to go.
 | 
			
		||||
                """),
 | 
			
		||||
    ],
 | 
			
		||||
    LocalConnection: [
 | 
			
		||||
        Parameter('password', kind=str,
 | 
			
		||||
                description="""
 | 
			
		||||
                Password to use for sudo. if not specified, the user will
 | 
			
		||||
                be prompted during intialization.
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('keep_password', kind=bool, default=True,
 | 
			
		||||
                description="""
 | 
			
		||||
                If ``True`` (the default), the password will be cached in
 | 
			
		||||
                memory after it is first obtained from the user, so that the
 | 
			
		||||
                user would not be prompted for it again.
 | 
			
		||||
                """),
 | 
			
		||||
        Parameter('unrooted', kind=bool, default=False,
 | 
			
		||||
                description="""
 | 
			
		||||
                Indicate that the target should be considered unrooted; do not
 | 
			
		||||
                attempt sudo or ask the user for their password.
 | 
			
		||||
                """),
 | 
			
		||||
    ],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# name --> ((target_class, conn_class), params_list, defaults, assistant_class)
 | 
			
		||||
TARGETS = {
 | 
			
		||||
    'linux': (LinuxTarget, COMMON_TARGET_PARAMS, None),
 | 
			
		||||
    'android': (AndroidTarget, COMMON_TARGET_PARAMS +
 | 
			
		||||
    'linux': ((LinuxTarget, SshConnection), COMMON_TARGET_PARAMS, None),
 | 
			
		||||
    'android': ((AndroidTarget, AdbConnection), COMMON_TARGET_PARAMS +
 | 
			
		||||
                [Parameter('package_data_directory', kind=str, default='/data/data',
 | 
			
		||||
                           description='''
 | 
			
		||||
                           Directory containing Android data
 | 
			
		||||
                           '''),
 | 
			
		||||
                ], None),
 | 
			
		||||
    'local': (LocalLinuxTarget, COMMON_TARGET_PARAMS, None),
 | 
			
		||||
    'local': ((LocalLinuxTarget, LocalConnection), COMMON_TARGET_PARAMS, None),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# name --> assistant
 | 
			
		||||
@@ -303,17 +378,19 @@ class DefaultTargetDescriptor(TargetDescriptor):
 | 
			
		||||
    def get_descriptions(self):
 | 
			
		||||
        result = []
 | 
			
		||||
        for target_name, target_tuple in TARGETS.iteritems():
 | 
			
		||||
            target, target_params = self._get_item(target_tuple)
 | 
			
		||||
            (target, conn), target_params = self._get_item(target_tuple)
 | 
			
		||||
            assistant = ASSISTANTS[target_name]
 | 
			
		||||
            conn_params =  CONNECTION_PARAMS[conn]
 | 
			
		||||
            for platform_name, platform_tuple in PLATFORMS.iteritems():
 | 
			
		||||
                platform, platform_params = self._get_item(platform_tuple)
 | 
			
		||||
 | 
			
		||||
                name = '{}_{}'.format(platform_name, target_name)
 | 
			
		||||
                td = TargetDescription(name, self)
 | 
			
		||||
                td.target = target
 | 
			
		||||
                td.conn = conn
 | 
			
		||||
                td.platform = platform
 | 
			
		||||
                td.assistant = assistant
 | 
			
		||||
                td.target_params = target_params
 | 
			
		||||
                td.conn_params = conn_params
 | 
			
		||||
                td.platform_params = platform_params
 | 
			
		||||
                td.assistant_params = assistant.parameters
 | 
			
		||||
                result.append(td)
 | 
			
		||||
 
 | 
			
		||||
@@ -107,9 +107,9 @@ class ApkWorkload(Workload):
 | 
			
		||||
    package_names = []
 | 
			
		||||
 | 
			
		||||
    parameters = [
 | 
			
		||||
        Parameter('package', kind=str,
 | 
			
		||||
        Parameter('package_name', kind=str,
 | 
			
		||||
                  description="""
 | 
			
		||||
                  The pacakge name that can be used to specify
 | 
			
		||||
                  The package name that can be used to specify
 | 
			
		||||
                  the workload apk to use.
 | 
			
		||||
                  """),
 | 
			
		||||
        Parameter('install_timeout', kind=int,
 | 
			
		||||
@@ -153,10 +153,14 @@ class ApkWorkload(Workload):
 | 
			
		||||
                  """)
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def package(self):
 | 
			
		||||
        return self.apk.package
 | 
			
		||||
 | 
			
		||||
    def __init__(self, target, **kwargs):
 | 
			
		||||
        super(ApkWorkload, self).__init__(target, **kwargs)
 | 
			
		||||
        self.apk = PackageHandler(self,
 | 
			
		||||
                                  package=self.package,
 | 
			
		||||
                                  package_name=self.package_name,
 | 
			
		||||
                                  variant=self.variant,
 | 
			
		||||
                                  strict=self.strict,
 | 
			
		||||
                                  version=self.version,
 | 
			
		||||
@@ -384,8 +388,9 @@ class ReventGUI(object):
 | 
			
		||||
 | 
			
		||||
    def setup(self):
 | 
			
		||||
        self._check_revent_files()
 | 
			
		||||
        self.revent_recorder.replay(self.on_target_setup_revent,
 | 
			
		||||
                                    timeout=self.setup_timeout)
 | 
			
		||||
        if self.revent_setup_file:
 | 
			
		||||
            self.revent_recorder.replay(self.on_target_setup_revent,
 | 
			
		||||
                                        timeout=self.setup_timeout)
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        msg = 'Replaying {}'
 | 
			
		||||
@@ -429,8 +434,14 @@ class ReventGUI(object):
 | 
			
		||||
 | 
			
		||||
class PackageHandler(object):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def package(self):
 | 
			
		||||
        if self.apk_info is None:
 | 
			
		||||
            return None
 | 
			
		||||
        return self.apk_info.package
 | 
			
		||||
 | 
			
		||||
    def __init__(self, owner, install_timeout=300, version=None, variant=None,
 | 
			
		||||
                 package=None, strict=False, force_install=False, uninstall=False,
 | 
			
		||||
                 package_name=None, strict=False, force_install=False, uninstall=False,
 | 
			
		||||
                 exact_abi=False):
 | 
			
		||||
        self.logger = logging.getLogger('apk')
 | 
			
		||||
        self.owner = owner
 | 
			
		||||
@@ -438,7 +449,7 @@ class PackageHandler(object):
 | 
			
		||||
        self.install_timeout = install_timeout
 | 
			
		||||
        self.version = version
 | 
			
		||||
        self.variant = variant
 | 
			
		||||
        self.package = package
 | 
			
		||||
        self.package_name = package_name
 | 
			
		||||
        self.strict = strict
 | 
			
		||||
        self.force_install = force_install
 | 
			
		||||
        self.uninstall = uninstall
 | 
			
		||||
@@ -462,7 +473,7 @@ class PackageHandler(object):
 | 
			
		||||
        self.apk_file = context.resolver.get(ApkFile(self.owner,
 | 
			
		||||
                                                     variant=self.variant,
 | 
			
		||||
                                                     version=self.version,
 | 
			
		||||
                                                     package=self.package,
 | 
			
		||||
                                                     package=self.package_name,
 | 
			
		||||
                                                     exact_abi=self.exact_abi,
 | 
			
		||||
                                                     supported_abi=self.supported_abi),
 | 
			
		||||
                                             strict=self.strict)
 | 
			
		||||
@@ -471,19 +482,19 @@ class PackageHandler(object):
 | 
			
		||||
            if self.version:
 | 
			
		||||
                installed_version = self.target.get_package_version(self.apk_info.package)
 | 
			
		||||
                host_version = self.apk_info.version_name
 | 
			
		||||
                if (installed_version != host_version and
 | 
			
		||||
                if (installed_version and installed_version != host_version and
 | 
			
		||||
                        loose_version_matching(self.version, installed_version)):
 | 
			
		||||
                    msg = 'Multiple matching packages found for {}; host version: {}, device version: {}'
 | 
			
		||||
                    raise WorkloadError(msg.format(self.owner, host_version, installed_version))
 | 
			
		||||
        else:
 | 
			
		||||
            if not self.owner.package_names and not self.package:
 | 
			
		||||
            if not self.owner.package_names and not self.package_name:
 | 
			
		||||
                msg = 'No package name(s) specified and no matching APK file found on host'
 | 
			
		||||
                raise WorkloadError(msg)
 | 
			
		||||
            self.resolve_package_from_target(context)
 | 
			
		||||
 | 
			
		||||
    def resolve_package_from_target(self, context):
 | 
			
		||||
        if self.package:
 | 
			
		||||
            if not self.target.package_is_installed(self.package):
 | 
			
		||||
        if self.package_name:
 | 
			
		||||
            if not self.target.package_is_installed(self.package_name):
 | 
			
		||||
                msg = 'Package "{}" cannot be found on the host or device'
 | 
			
		||||
                raise WorkloadError(msg.format(self.package_name))
 | 
			
		||||
        else:
 | 
			
		||||
@@ -496,23 +507,23 @@ class PackageHandler(object):
 | 
			
		||||
                for package in installed_versions:
 | 
			
		||||
                    package_version = self.target.get_package_version(package)
 | 
			
		||||
                    if loose_version_matching(self.version, package_version):
 | 
			
		||||
                        self.package = package
 | 
			
		||||
                        self.package_name = package
 | 
			
		||||
                        break
 | 
			
		||||
            else:
 | 
			
		||||
                if len(installed_versions) == 1:
 | 
			
		||||
                    self.package = installed_versions[0]
 | 
			
		||||
                    self.package_name = installed_versions[0]
 | 
			
		||||
                else:
 | 
			
		||||
                    msg = 'Package version not set and multiple versions found on device'
 | 
			
		||||
                    raise WorkloadError(msg)
 | 
			
		||||
 | 
			
		||||
            if not self.package:
 | 
			
		||||
            if not self.package_name:
 | 
			
		||||
                raise WorkloadError('No matching package found')
 | 
			
		||||
 | 
			
		||||
        self.pull_apk(self.package)
 | 
			
		||||
        self.pull_apk(self.package_name)
 | 
			
		||||
        self.apk_file = context.resolver.get(ApkFile(self.owner,
 | 
			
		||||
                                                     variant=self.variant,
 | 
			
		||||
                                                     version=self.version,
 | 
			
		||||
                                                     package=self.package),
 | 
			
		||||
                                                     package=self.package_name),
 | 
			
		||||
                                             strict=self.strict)
 | 
			
		||||
        self.apk_info = ApkInfo(self.apk_file)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,12 +18,12 @@
 | 
			
		||||
from __future__ import division
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from devlib import DerivedEnergyMeasurements
 | 
			
		||||
from devlib.instrument import CONTINUOUS
 | 
			
		||||
from devlib.instrument.energy_probe import EnergyProbeInstrument
 | 
			
		||||
from devlib.instrument.daq import DaqInstrument
 | 
			
		||||
from devlib.instrument.acmecape import AcmeCapeInstrument
 | 
			
		||||
from devlib.utils.misc import which
 | 
			
		||||
from devlib.derived.derived_measurements import DerivedEnergyMeasurements
 | 
			
		||||
 | 
			
		||||
from wa import Instrument, Parameter
 | 
			
		||||
from wa.framework import pluginloader
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <sys/ioctl.h>
 | 
			
		||||
#include <sys/stat.h>
 | 
			
		||||
@@ -49,8 +50,10 @@
 | 
			
		||||
 | 
			
		||||
const char MAGIC[] = "REVENT";
 | 
			
		||||
 | 
			
		||||
// NOTE: This should be incremented if any changes are made to the file format
 | 
			
		||||
uint16_t FORMAT_VERSION = 2;
 | 
			
		||||
// NOTE: This should be incremented if any changes are made to the file format.
 | 
			
		||||
//       Should that be the case, also make sure to update the format description
 | 
			
		||||
//       in doc/source/revent.rst and the Python parser in wa/utils/revent.py.
 | 
			
		||||
uint16_t FORMAT_VERSION = 3;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
	FALSE=0,
 | 
			
		||||
@@ -117,6 +120,8 @@ typedef struct {
 | 
			
		||||
	input_devices_t devices;
 | 
			
		||||
	device_info_t *gamepad_info;
 | 
			
		||||
	uint64_t num_events;
 | 
			
		||||
	struct timeval start_time;
 | 
			
		||||
	struct timeval end_time;
 | 
			
		||||
	replay_event_t *events;
 | 
			
		||||
} revent_recording_t;
 | 
			
		||||
 | 
			
		||||
@@ -268,14 +273,17 @@ void adjust_event_times(revent_recording_t *recording)
 | 
			
		||||
	if (recording->num_events == 0)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	time_zero.tv_sec = recording->events[0].event.time.tv_sec;
 | 
			
		||||
	time_zero.tv_usec = recording->events[0].event.time.tv_usec;
 | 
			
		||||
	time_zero.tv_sec = recording->start_time.tv_sec;
 | 
			
		||||
	time_zero.tv_usec = recording->start_time.tv_usec;
 | 
			
		||||
 | 
			
		||||
	for(i = 0; i < recording->num_events; i++) {
 | 
			
		||||
		timersub(&recording->events[i].event.time, &time_zero, &time_delta);
 | 
			
		||||
		recording->events[i].event.time.tv_sec = time_delta.tv_sec;
 | 
			
		||||
		recording->events[i].event.time.tv_usec = time_delta.tv_usec;
 | 
			
		||||
	}
 | 
			
		||||
	timersub(&recording->end_time, &time_zero, &time_delta);
 | 
			
		||||
	recording->end_time.tv_sec = time_delta.tv_sec;
 | 
			
		||||
	recording->end_time.tv_usec = time_delta.tv_usec;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int write_record_header(int fd, const revent_record_desc_t *desc)
 | 
			
		||||
@@ -559,6 +567,28 @@ void print_device_info(device_info_t *info)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int read_record_timestamps(FILE *fin, revent_recording_t *recording)
 | 
			
		||||
{
 | 
			
		||||
	int ret;
 | 
			
		||||
	ret = fread(&recording->start_time.tv_sec, sizeof(uint64_t), 1, fin);
 | 
			
		||||
	if (ret < 1)
 | 
			
		||||
		return errno;
 | 
			
		||||
 | 
			
		||||
	ret = fread(&recording->start_time.tv_usec, sizeof(uint64_t), 1, fin);
 | 
			
		||||
	if (ret < 1)
 | 
			
		||||
		return errno;
 | 
			
		||||
 | 
			
		||||
	ret = fread(&recording->end_time.tv_sec, sizeof(uint64_t), 1, fin);
 | 
			
		||||
	if (ret < 1)
 | 
			
		||||
		return errno;
 | 
			
		||||
 | 
			
		||||
	ret = fread(&recording->end_time.tv_usec, sizeof(uint64_t), 1, fin);
 | 
			
		||||
	if (ret < 1)
 | 
			
		||||
		return errno;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int write_replay_event(FILE *fout, const replay_event_t *ev)
 | 
			
		||||
{
 | 
			
		||||
	size_t ret;
 | 
			
		||||
@@ -982,13 +1012,33 @@ inline void read_revent_recording_or_die(const char *filepath, revent_recording_
 | 
			
		||||
		if (ret < 1)
 | 
			
		||||
			die("Could not read the number of recorded events");
 | 
			
		||||
 | 
			
		||||
		if (recording->desc.version > 2) {
 | 
			
		||||
			ret = read_record_timestamps(fin, recording);
 | 
			
		||||
			if (ret)
 | 
			
		||||
				die("Could not read recroding timestamps.");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		recording->events = malloc(sizeof(replay_event_t) * recording->num_events);
 | 
			
		||||
		if (recording->events == NULL)
 | 
			
		||||
			die("Not enough memory to allocate replay buffer");
 | 
			
		||||
 | 
			
		||||
		for(i=0; i < recording->num_events; i++) {
 | 
			
		||||
		// start/end times tracking for recording as a whole was added in version 3
 | 
			
		||||
		// of recording format; for earlier recordings, use timestamps of the first and
 | 
			
		||||
		// last events.
 | 
			
		||||
		read_replay_event(fin, &recording->events[0]);
 | 
			
		||||
		if (recording->desc.version <= 2) {
 | 
			
		||||
			recording->start_time.tv_sec  = recording->events[0].event.time.tv_sec;
 | 
			
		||||
			recording->start_time.tv_usec  = recording->events[0].event.time.tv_usec;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for(i=1; i < recording->num_events; i++) {
 | 
			
		||||
			read_replay_event(fin, &recording->events[i]);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (recording->desc.version <= 2) {
 | 
			
		||||
			recording->end_time.tv_sec  = recording->events[i].event.time.tv_sec;
 | 
			
		||||
			recording->end_time.tv_usec  = recording->events[i].event.time.tv_usec;
 | 
			
		||||
		}
 | 
			
		||||
	} else {   // backwards compatibility
 | 
			
		||||
		/* Prior to verion 2, the total number of recorded events was not being
 | 
			
		||||
		 * written as part of the recording. We will use the size of the file on
 | 
			
		||||
@@ -1039,6 +1089,7 @@ void exitHandler(int z) {
 | 
			
		||||
void record(const char *filepath, int delay, recording_mode_t mode)
 | 
			
		||||
{
 | 
			
		||||
	int ret;
 | 
			
		||||
	struct timespec start_time, end_time;
 | 
			
		||||
	FILE *fout = init_recording(filepath, mode);
 | 
			
		||||
	if (fout == NULL)
 | 
			
		||||
		die("Could not create recording \"%s\": %s", filepath, strerror(errno));
 | 
			
		||||
@@ -1075,10 +1126,11 @@ void record(const char *filepath, int delay, recording_mode_t mode)
 | 
			
		||||
 | 
			
		||||
	// Write the zero size as a place holder and remember the position in the
 | 
			
		||||
	// file stream, so that it may be updated at the end with the actual event
 | 
			
		||||
	// count.
 | 
			
		||||
	// count. Reserving space for five uint64_t's -- the number of events and
 | 
			
		||||
	// end time stamps.
 | 
			
		||||
	uint64_t event_count = 0;
 | 
			
		||||
	long size_pos = ftell(fout);
 | 
			
		||||
	ret = fwrite(&event_count, sizeof(uint64_t), 1, fout);
 | 
			
		||||
	ret = fwrite(&event_count, sizeof(uint64_t), 5, fout);
 | 
			
		||||
	if (ret < 1)
 | 
			
		||||
		die("Could not initialise event count: %s", strerror(errno));
 | 
			
		||||
 | 
			
		||||
@@ -1096,6 +1148,7 @@ void record(const char *filepath, int delay, recording_mode_t mode)
 | 
			
		||||
	errno = 0;
 | 
			
		||||
	signal(SIGINT, exitHandler);
 | 
			
		||||
	
 | 
			
		||||
	clock_gettime(CLOCK_MONOTONIC_RAW, &start_time);
 | 
			
		||||
	while(1)
 | 
			
		||||
	{
 | 
			
		||||
		FD_ZERO(&readfds);
 | 
			
		||||
@@ -1160,6 +1213,7 @@ void record(const char *filepath, int delay, recording_mode_t mode)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	clock_gettime(CLOCK_MONOTONIC_RAW, &end_time);
 | 
			
		||||
 | 
			
		||||
	dprintf("Writing event count...");
 | 
			
		||||
	if ((ret = fseek(fout, size_pos, SEEK_SET)) == -1)
 | 
			
		||||
@@ -1167,6 +1221,16 @@ void record(const char *filepath, int delay, recording_mode_t mode)
 | 
			
		||||
	ret = fwrite(&event_count, sizeof(uint64_t), 1, fout);
 | 
			
		||||
	if (ret < 1)
 | 
			
		||||
		die("Could not write event count: %s", strerror(errno));
 | 
			
		||||
	dprintf("Writing recording timestamps...");
 | 
			
		||||
	uint64_t usecs;
 | 
			
		||||
	fwrite(&start_time.tv_sec, sizeof(uint64_t), 1, fout);
 | 
			
		||||
	usecs = start_time.tv_nsec / 1000;
 | 
			
		||||
	fwrite(&usecs, sizeof(uint64_t), 1, fout);
 | 
			
		||||
	fwrite(&end_time.tv_sec, sizeof(uint64_t), 1, fout);
 | 
			
		||||
	usecs = end_time.tv_nsec / 1000;
 | 
			
		||||
	ret = fwrite(&usecs, sizeof(uint64_t), 1, fout);
 | 
			
		||||
	if (ret < 1)
 | 
			
		||||
		die("Could not write recording timestamps: %s", strerror(errno));
 | 
			
		||||
 | 
			
		||||
	fclose(fout);
 | 
			
		||||
 | 
			
		||||
@@ -1190,6 +1254,8 @@ void dump(const char *filepath)
 | 
			
		||||
	printf("recording version: %u\n", recording.desc.version);
 | 
			
		||||
	printf("recording type: %i\n", recording.desc.mode);
 | 
			
		||||
	printf("number of recorded events: %lu\n", recording.num_events);
 | 
			
		||||
	printf("start time: %ld.%06ld \n", recording.start_time.tv_sec, recording.start_time.tv_usec);
 | 
			
		||||
	printf("end time:   %ld.%06ld \n", recording.end_time.tv_sec, recording.end_time.tv_usec);
 | 
			
		||||
 | 
			
		||||
	printf("\n");
 | 
			
		||||
	if (recording.desc.mode == GENERAL_MODE) {
 | 
			
		||||
@@ -1264,18 +1330,35 @@ void replay(const char *filepath)
 | 
			
		||||
 | 
			
		||||
		int32_t idx = (recording.events[i]).dev_idx;
 | 
			
		||||
		struct input_event ev = (recording.events[i]).event;
 | 
			
		||||
		while((i < recording.num_events) && !timercmp(&ev.time, &last_event_delta, !=)) {
 | 
			
		||||
		while(!timercmp(&ev.time, &last_event_delta, !=)) {
 | 
			
		||||
			ret = write(recording.devices.fds[idx], &ev, sizeof(ev));
 | 
			
		||||
			if (ret != sizeof(ev))
 | 
			
		||||
				die("Could not replay event");
 | 
			
		||||
			dprintf("replayed event: type %d code %d value %d\n", ev.type, ev.code, ev.value);
 | 
			
		||||
 | 
			
		||||
			i++;
 | 
			
		||||
			if (i >= recording.num_events) {
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			idx = recording.events[i].dev_idx;
 | 
			
		||||
			ev = recording.events[i].event;
 | 
			
		||||
		}
 | 
			
		||||
		last_event_delta = ev.time;
 | 
			
		||||
	}
 | 
			
		||||
	timeradd(&start_time, &recording.end_time, &desired_time);
 | 
			
		||||
	gettimeofday(&now, NULL);
 | 
			
		||||
	if (timercmp(&desired_time, &now, >)) {
 | 
			
		||||
		timersub(&desired_time, &now, &delta);
 | 
			
		||||
		useconds_t d = (useconds_t)delta.tv_sec * 1000000 + delta.tv_usec;
 | 
			
		||||
		dprintf("now %u.%u recording end time %u.%u sleeping %u uS\n",
 | 
			
		||||
				(unsigned int)now.tv_sec,
 | 
			
		||||
				(unsigned int)now.tv_usec,
 | 
			
		||||
				(unsigned int)desired_time.tv_sec,
 | 
			
		||||
				(unsigned int)desired_time.tv_usec,
 | 
			
		||||
				d);
 | 
			
		||||
		usleep(d);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (recording.desc.mode == GAMEPAD_MODE)
 | 
			
		||||
		destroy_replay_device(recording.devices.fds[0]);
 | 
			
		||||
 
 | 
			
		||||
@@ -165,6 +165,8 @@ def log_error(e, logger, critical=False):
 | 
			
		||||
        log_func(tb)
 | 
			
		||||
        log_func('{}({})'.format(e.__class__.__name__, e))
 | 
			
		||||
 | 
			
		||||
    e.logged = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ErrorSignalHandler(logging.Handler):
 | 
			
		||||
    """
 | 
			
		||||
 
 | 
			
		||||
@@ -202,7 +202,7 @@ class ReventRecording(object):
 | 
			
		||||
            raise ValueError(msg.format(self.filepath))
 | 
			
		||||
        self.version = version
 | 
			
		||||
 | 
			
		||||
        if self.version == 2:
 | 
			
		||||
        if 3 >= self.version >= 2:
 | 
			
		||||
            self.mode, = read_struct(fh, header_two_struct)
 | 
			
		||||
            if self.mode == GENERAL_MODE:
 | 
			
		||||
                self._read_devices(fh)
 | 
			
		||||
@@ -211,6 +211,14 @@ class ReventRecording(object):
 | 
			
		||||
            else:
 | 
			
		||||
                raise ValueError('Unexpected recording mode: {}'.format(self.mode))
 | 
			
		||||
            self.num_events, = read_struct(fh, u64_struct)
 | 
			
		||||
            if self.version > 2:
 | 
			
		||||
                ts_sec = read_struct(fh, u64_struct)
 | 
			
		||||
                ts_usec = read_struct(fh, u64_struct)
 | 
			
		||||
                self.start_time = datetime.fromtimestamp(ts_sec + float(ts_usec) / 1000000)
 | 
			
		||||
                ts_sec = read_struct(fh, u64_struct)
 | 
			
		||||
                ts_usec = read_struct(fh, u64_struct)
 | 
			
		||||
                self.end_time = datetime.fromtimestamp(ts_sec + float(ts_usec) / 1000000)
 | 
			
		||||
 | 
			
		||||
        elif 2 > self.version >= 0:
 | 
			
		||||
            self.mode = GENERAL_MODE
 | 
			
		||||
            self._read_devices(fh)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								wa/workloads/templerun2/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								wa/workloads/templerun2/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
#    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 TempleRun2(ReventWorkload):
 | 
			
		||||
 | 
			
		||||
    name = 'templerun2'
 | 
			
		||||
    description = """
 | 
			
		||||
    Temple Run 2 game.
 | 
			
		||||
 | 
			
		||||
    Sequel to Temple Run. 3D on-the-rails racer.
 | 
			
		||||
    """
 | 
			
		||||
    view = 'SurfaceView - com.imangi.templerun2/com.imangi.unityactivity.ImangiUnityNativeActivity'
 | 
			
		||||
		Reference in New Issue
	
	Block a user