aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergei Trofimov <sergei.trofimov@arm.com>2017-09-25 11:52:49 +0100
committerSergei Trofimov <sergei.trofimov@arm.com>2017-09-27 10:34:00 +0100
commit0c40cdae18509f6b0ecf6cd84da45e415ed611d1 (patch)
treef6dd6e12524c5e8a7a71b32331b0b24d799ff935
parent0934037e1b42f913a9f35984100e1df06f475ef1 (diff)
tools/revent: improve recording + add prebuilts
The begining and end of recording timestamps are now stored as part of the recording. This allows to correctly replay recordings where the first event occurs some time after the recording started, or the last event some time before recording ended. Add pre-built revent binaries for the armeabi and arm64 architectures.
-rw-r--r--doc/source/revent.rst393
-rwxr-xr-xwa/assets/bin/arm64/reventbin0 -> 4976640 bytes
-rwxr-xr-xwa/assets/bin/armeabi/reventbin0 -> 2858568 bytes
-rw-r--r--wa/tools/revent/revent.c99
-rw-r--r--wa/utils/revent.py12
5 files changed, 493 insertions, 11 deletions
diff --git a/doc/source/revent.rst b/doc/source/revent.rst
index 9f52c342..d0954ee2 100644
--- a/doc/source/revent.rst
+++ b/doc/source/revent.rst
@@ -100,3 +100,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)
diff --git a/wa/assets/bin/arm64/revent b/wa/assets/bin/arm64/revent
new file mode 100755
index 00000000..6d5f9e69
--- /dev/null
+++ b/wa/assets/bin/arm64/revent
Binary files differ
diff --git a/wa/assets/bin/armeabi/revent b/wa/assets/bin/armeabi/revent
new file mode 100755
index 00000000..8aedc3d4
--- /dev/null
+++ b/wa/assets/bin/armeabi/revent
Binary files differ
diff --git a/wa/tools/revent/revent.c b/wa/tools/revent/revent.c
index 3dc50ed3..801cf0ec 100644
--- a/wa/tools/revent/revent.c
+++ b/wa/tools/revent/revent.c
@@ -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]);
diff --git a/wa/utils/revent.py b/wa/utils/revent.py
index 5ecfa5de..515c9087 100644
--- a/wa/utils/revent.py
+++ b/wa/utils/revent.py
@@ -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)
@@ -281,8 +289,6 @@ class ReventRecorder(object):
def stop_record(self):
self.target.killall('revent', signal.SIGINT, as_root=self.target.is_rooted)
- self.target.sleep(1)
- self.target.execute('sync')
def replay(self, revent_file, timeout=None):
self.target.killall('revent')