1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2024-10-06 19:01:15 +01:00

Merge pull request #200 from ep1cman/revent_fixes

revent: Fixed dump command segfault
This commit is contained in:
setrofim 2016-07-19 16:54:53 +01:00 committed by GitHub
commit a2d0747b4c
8 changed files with 348 additions and 54 deletions

View File

@ -106,3 +106,107 @@ 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).
File format of revent
=====================
.. note:: All values below are little endian
Recording structure of revent
-----------------------------
revent recordings are made of of five parts:
* A "magic" string of `REVENT` to help identify revent recordings.
* A unsigned integer representing the revent file format version.
* A signed integer that gives the number of devices in this recording.
* A series of device paths, the number of which is given in the previous field.
For more detail see `Device path structure`_ below.
* An unlimited number of recorded events. For more detail see `Event Structure`_
below.
::
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| MAGIC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| MAGIC cont. | Version |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of devices |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Device paths +-+-+-+-+-+-+-+-+-+-+-+-+
| | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| Events |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Device path structure
----------------------
This part of an revent recording is used to store the paths to input devices used in the
recording. It consists of:
* A signed integer giving the size of the following string.
* A string, with a maximum length of 30, containing the path of an input device.
::
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 |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Event structure
---------------
The majority of an revent recording will be made up of the input events that were
recorded. There and be an unlimited number of these events in an revent file and they
are structured as follows:
* A signed 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.
* 32 bits of padding
* 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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PADDING |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp Seconds |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp Seconds cont. |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp Micoseconds |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp Micoseconds cont. |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Event Type | Event Code |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Event Value |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

View File

@ -15,6 +15,8 @@
import os
import sys
import signal
from math import ceil
from wlauto import ExtensionLoader, Command, settings
from wlauto.common.resources import Executable
@ -22,6 +24,7 @@ from wlauto.core.resource import NO_ONE
from wlauto.core.resolver import ResourceResolver
from wlauto.core.configuration import RunConfiguration
from wlauto.core.agenda import Agenda
from wlauto.utils.revent import ReventParser
class RecordCommand(Command):
@ -114,13 +117,15 @@ class RecordCommand(Command):
self.logger.info("Press Enter when you are ready to record...")
raw_input("")
command = "{} record -t 100000 -s {}".format(self.target_binary, revent_file)
command = "{} record -s {}".format(self.target_binary, revent_file)
self.device.kick_off(command)
self.logger.info("Press Enter when you have finished recording...")
raw_input("")
self.device.killall("revent")
self.device.killall("revent", signal.SIGTERM)
self.logger.info("Waiting for revent to finish")
while self.device.get_pids_of("revent"):
pass
self.logger.info("Pulling files from device")
self.device.pull_file(revent_file, args.output or os.getcwdu())
@ -154,8 +159,10 @@ class ReplayCommand(RecordCommand):
self.logger.info("Starting {}".format(args.package))
self.device.execute('monkey -p {} -c android.intent.category.LAUNCHER 1'.format(args.package))
self.logger.info("Replaying recording")
command = "{} replay {}".format(self.target_binary, revent_file)
self.device.execute(command)
timeout = ceil(ReventParser.get_revent_duration(args.revent)) + 30
self.device.execute(command, timeout=timeout)
self.logger.info("Finished replay")

View File

@ -16,6 +16,7 @@
import os
import sys
import time
from math import ceil
from wlauto.core.extension import Parameter
from wlauto.core.workload import Workload
@ -25,6 +26,7 @@ from wlauto.common.resources import ExtensionAsset, Executable
from wlauto.exceptions import WorkloadError, ResourceError, ConfigError
from wlauto.utils.android import ApkInfo, ANDROID_NORMAL_PERMISSIONS
from wlauto.utils.types import boolean
from wlauto.utils.revent import ReventParser
import wlauto.common.android.resources
@ -322,16 +324,13 @@ AndroidBenchmark = ApkWorkload # backward compatibility
class ReventWorkload(Workload):
default_setup_timeout = 5 * 60 # in seconds
default_run_timeout = 10 * 60 # in seconds
def __init__(self, device, _call_super=True, **kwargs):
if _call_super:
super(ReventWorkload, self).__init__(device, **kwargs)
devpath = self.device.path
self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent')
self.setup_timeout = kwargs.get('setup_timeout', self.default_setup_timeout)
self.run_timeout = kwargs.get('run_timeout', self.default_run_timeout)
self.setup_timeout = kwargs.get('setup_timeout', None)
self.run_timeout = kwargs.get('run_timeout', None)
self.revent_setup_file = None
self.revent_run_file = None
self.on_device_setup_revent = None
@ -346,6 +345,10 @@ class ReventWorkload(Workload):
self.on_device_run_revent = devpath.join(self.device.working_directory,
os.path.split(self.revent_run_file)[-1])
self._check_revent_files(context)
default_setup_timeout = ceil(ReventParser.get_revent_duration(self.revent_setup_file)) + 30
default_run_timeout = ceil(ReventParser.get_revent_duration(self.revent_run_file)) + 30
self.setup_timeout = self.setup_timeout or default_setup_timeout
self.run_timeout = self.run_timeout or default_run_timeout
def setup(self, context):
self.device.killall('revent')

Binary file not shown.

Binary file not shown.

View File

@ -42,7 +42,10 @@
#define INPDEV_MAX_DEVICES 16
#define INPDEV_MAX_PATH 30
const char magic[] = "REVENT";
//This should be incremented if any changes are made to the file format
uint16_t file_version = 1;
#ifndef ANDROID
int strlcpy(char *dest, char *source, size_t size)
@ -110,6 +113,56 @@ bool_t is_numeric(char *string)
return TRUE;
}
// Has to be done this explicitly to maintain compatibility between
// 32-bit and 64-bit devices
int read_replay_event(int fdin, replay_event_t* ev)
{
size_t rb;
rb = read(fdin, &(ev->dev_idx), sizeof(int32_t));
if (rb < (int)sizeof(int32_t)) return -1;
rb = read(fdin, &(ev->_padding), sizeof(int32_t));
if (rb < (int)sizeof(int32_t)) return -1;
struct timeval time;
uint64_t temp_time;
rb = read(fdin, &temp_time, sizeof(uint64_t));
if (rb < (int)sizeof(uint64_t)) return -1;
time.tv_sec = (time_t)temp_time;
rb = read(fdin, &temp_time, sizeof(uint64_t));
if (rb < (int)sizeof(uint64_t)) return -1;
time.tv_usec = (suseconds_t)temp_time;
ev->event.time = time;
rb = read(fdin, &(ev->event.type), sizeof(uint16_t));
if (rb < (int)sizeof(uint16_t)) return -1;
rb = read(fdin, &(ev->event.code), sizeof(uint16_t));
if (rb < (int)sizeof(uint16_t)) return -1;
rb = read(fdin, &(ev->event.value), sizeof(int32_t));
if (rb < (int)sizeof(int32_t)) return -1;
return 0;
}
void write_input_event(FILE * fdout, struct input_event* ev)
{
uint64_t time;
time = (uint64_t)ev->time.tv_sec;
fwrite(&time, sizeof(uint64_t), 1, fdout);
time = (uint64_t)ev->time.tv_usec;
fwrite(&time, sizeof(uint64_t), 1, fdout);
fwrite(&(ev->type), sizeof(uint16_t), 1, fdout);
fwrite(&(ev->code), sizeof(uint16_t), 1, fdout);
fwrite(&(ev->value), sizeof(int32_t), 1, fdout);
}
off_t get_file_size(const char *filename) {
struct stat st;
@ -191,41 +244,52 @@ void dump(const char *logfile)
int fdin = open(logfile, O_RDONLY);
if (fdin < 0) die("Could not open eventlog %s\n", logfile);
int nfds;
size_t rb = read(fdin, &nfds, sizeof(nfds));
if (rb != sizeof(nfds)) die("problems reading eventlog\n");
int *fds = malloc(sizeof(int)*nfds);
if (!fds) die("out of memory\n");
int32_t len;
int32_t i;
char buf[INPDEV_MAX_PATH];
//Read magic
len = strlen(magic);
size_t rb = read(fdin, &buf[0], len);
if (rb != len) die("problems reading eventlog\n");
if(strcmp(magic, buf) != 0)
die("File is not an revent recording, are you using an old recording?");
//Read file format version
uint16_t version;
rb = read(fdin, &version, sizeof(version));
if (rb != sizeof(version)) die("problems reading eventlog\n");
printf("File format version: %i\n", version);
int32_t nfds;
rb = read(fdin, &nfds, sizeof(nfds));
if (rb != sizeof(nfds)) die("problems reading eventlog\n");
int *fds = malloc(sizeof(int)*nfds);
if (!fds) die("out of memory\n");
inpdev_t *inpdev = malloc(sizeof(inpdev_t));
inpdev->id_pathc = 0;
inpdev->id_pathc = nfds;
for (i=0; i<nfds; i++) {
memset(buf, 0, sizeof(buf));
rb = read(fdin, &len, sizeof(len));
if (rb != sizeof(len)) die("problems reading eventlog\n");
if (len >= INPDEV_MAX_PATH) die("path length too long, file corrupt");
rb = read(fdin, &buf[0], len);
if (rb != len) die("problems reading eventlog\n");
strlcpy(inpdev->id_pathv[inpdev->id_pathc], buf, INPDEV_MAX_PATH);
inpdev->id_pathv[inpdev->id_pathc][INPDEV_MAX_PATH-1] = '\0';
inpdev->id_pathc++;
}
replay_event_t rep_ev;
struct input_event ev;
int count = 0;
while(1) {
int32_t idx;
rb = read(fdin, &idx, sizeof(idx));
if (rb != sizeof(idx)) break;
rb = read(fdin, &ev, sizeof(ev));
if (rb < (int)sizeof(ev)) break;
if (read_replay_event(fdin, &rep_ev) == -1)
break;
ev = rep_ev.event;
printf("%10u.%-6u %30s type %2d code %3d value %4d\n",
(unsigned int)ev.time.tv_sec, (unsigned int)ev.time.tv_usec,
inpdev->id_pathv[idx], ev.type, ev.code, ev.value);
inpdev->id_pathv[rep_ev.dev_idx], ev.type, ev.code, ev.value);
count++;
}
@ -247,7 +311,22 @@ int replay_buffer_init(replay_buffer_t **buffer, const char *logfile)
if (fdin < 0)
die("Could not open eventlog %s\n", logfile);
size_t rb = read(fdin, &(buff->num_fds), sizeof(buff->num_fds));
int32_t len, i;
//Read magic
char buf[7];
len = strlen(magic);
size_t rb = read(fdin, &buf[0], len);
if (rb != len) die("problems reading eventlog\n");
if(strcmp(magic, buf) != 0)
die("File is not an revent recording, are you using an old recording?");
//Read file format version
uint16_t version;
rb = read(fdin, &version, sizeof(version));
if (rb != sizeof(version)) die("problems reading eventlog\n");
rb = read(fdin, &(buff->num_fds), sizeof(buff->num_fds));
if (rb!=sizeof(buff->num_fds))
die("problems reading eventlog\n");
@ -255,13 +334,15 @@ int replay_buffer_init(replay_buffer_t **buffer, const char *logfile)
if (!buff->fds)
die("out of memory\n");
int32_t len, i;
char path_buff[256]; // should be more than enough
char path_buff[INPDEV_MAX_PATH];
for (i = 0; i < buff->num_fds; i++) {
memset(path_buff, 0, sizeof(path_buff));
rb = read(fdin, &len, sizeof(len));
if (rb!=sizeof(len))
die("problems reading eventlog\n");
if (len >= INPDEV_MAX_PATH)
die("path length too long, file corrupt");
rb = read(fdin, &path_buff[0], len);
if (rb != len)
die("problems reading eventlog\n");
@ -275,8 +356,7 @@ int replay_buffer_init(replay_buffer_t **buffer, const char *logfile)
replay_event_t rep_ev;
i = 0;
while(1) {
rb = read(fdin, &rep_ev, sizeof(rep_ev));
if (rb < (int)sizeof(rep_ev))
if (read_replay_event(fdin, &rep_ev) == -1)
break;
if (i == 0) {
@ -369,6 +449,8 @@ void usage()
" -d DEVICE the number of the input device form which\n"
" events will be recoreded. If not specified, \n"
" all available inputs will be used.\n"
" -s Recording will not be stopped if there is \n"
" input on STDIN.\n"
"\n"
" replay FILE\n"
" replays previously recorded events from the specified file.\n"
@ -488,19 +570,7 @@ int count;
void term_handler(int signum)
{
int32_t i;
for (i=0; i < inpdev->id_pathc; i++)
{
close(fds[i]);
}
fclose(fdout);
free(fds);
dprintf("Recorded %d events\n", count);
inpdev_close(inpdev);
revent_args_close(rargs);
exit(0);
(void)signum;
}
void record(inpdev_t *inpdev, int delay, const char *logfile)
@ -524,6 +594,11 @@ void record(inpdev_t *inpdev, int delay, const char *logfile)
fdout = fopen(logfile, "wb");
if (!fdout) die("Could not open eventlog %s\n", logfile);
//Write magic & file format version
fwrite(&magic, strlen(magic), 1, fdout);
fwrite(&file_version, sizeof(file_version), 1, fdout);
//Write device paths
fwrite(&inpdev->id_pathc, sizeof(inpdev->id_pathc), 1, fdout);
for (i=0; i<inpdev->id_pathc; i++) {
int32_t len = strlen(inpdev->id_pathv[i]);
@ -536,11 +611,17 @@ void record(inpdev_t *inpdev, int delay, const char *logfile)
fds[i] = open(inpdev->id_pathv[i], O_RDONLY);
if (fds[i]>maxfd) maxfd = fds[i];
dprintf("opened %s with %d\n", inpdev->id_pathv[i], fds[i]);
if (fds[i]<0) die("could not open \%s\n", inpdev->id_pathv[i]);
if (fds[i]<0) die("could not open %s\n", inpdev->id_pathv[i]);
}
//Block SIGTERM
sigset_t sigset, oldset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGTERM);
sigprocmask(SIG_BLOCK, &sigset, &oldset);
count = 0;
struct timeval tout;
struct timespec tout;
while(1)
{
FD_ZERO(&readfds);
@ -552,10 +633,13 @@ void record(inpdev_t *inpdev, int delay, const char *logfile)
FD_SET(fds[i], &readfds);
/* wait for input */
tout.tv_sec = delay;
tout.tv_usec = 0;
int32_t r = select(maxfd+1, &readfds, NULL, NULL, &tout);
tout.tv_nsec = 0;
int32_t r = pselect(maxfd+1, &readfds, NULL, NULL, &tout, &oldset);
if (errno == EINTR)
break;
/* dprintf("got %d (err %d)\n", r, errno); */
if (!r) break;
if (!r)
break;
if (wait_for_stdin && FD_ISSET(STDIN_FILENO, &readfds)) {
// in this case the key down for the return key will be recorded
// so we need to up the key up
@ -566,12 +650,12 @@ void record(inpdev_t *inpdev, int delay, const char *logfile)
gettimeofday(&ev.time, NULL);
fwrite(&keydev, sizeof(keydev), 1, fdout);
fwrite(&_padding, sizeof(_padding), 1, fdout);
fwrite(&ev, sizeof(ev), 1, fdout);
write_input_event(fdout, &ev);
memset(&ev, 0, sizeof(ev)); // SYN
gettimeofday(&ev.time, NULL);
fwrite(&keydev, sizeof(keydev), 1, fdout);
fwrite(&_padding, sizeof(_padding), 1, fdout);
fwrite(&ev, sizeof(ev), 1, fdout);
write_input_event(fdout, &ev);
dprintf("added fake return exiting...\n");
break;
}
@ -589,7 +673,7 @@ void record(inpdev_t *inpdev, int delay, const char *logfile)
keydev = i;
fwrite(&i, sizeof(i), 1, fdout);
fwrite(&_padding, sizeof(_padding), 1, fdout);
fwrite(&ev, sizeof(ev), 1, fdout);
write_input_event(fdout, &ev);
count++;
}
}

View File

@ -33,6 +33,7 @@ from wlauto.exceptions import ResourceError
from wlauto.utils.android import ApkInfo
from wlauto.utils.misc import ensure_directory_exists as _d, ensure_file_directory_exists as _f, sha256, urljoin
from wlauto.utils.types import boolean
from wlauto.utils.revent import ReventParser
logging.getLogger("requests").setLevel(logging.WARNING)
@ -98,7 +99,12 @@ class ReventGetter(ResourceGetter):
location = _d(os.path.join(self.get_base_location(resource), 'revent_files'))
for candidate in os.listdir(location):
if candidate.lower() == filename.lower():
return os.path.join(location, candidate)
path = os.path.join(location, candidate)
try:
ReventParser.check_revent_file(path)
return path
except ValueError as e:
self.logger.warning(e.message)
class PackageApkGetter(PackageFileGetter):
@ -430,7 +436,11 @@ class HttpGetter(ResourceGetter):
for asset in assets:
pathname = os.path.basename(asset['path']).lower()
if pathname == filename:
return asset
try:
ReventParser.check_revent_file(asset['path'])
return asset
except ValueError as e:
self.logger.warning(e.message)
else: # file
for asset in assets:
if asset['path'].lower() == resource.path.lower():
@ -514,14 +524,22 @@ class RemoteFilerGetter(ResourceGetter):
# There tends to be some confusion as to where revent files should
# be placed. This looks both in the extension's directory, and in
# 'revent_files' subdirectory under it, if it exists.
path = None
if os.path.isdir(alternate_location):
for candidate in os.listdir(alternate_location):
if candidate.lower() == filename.lower():
return os.path.join(alternate_location, candidate)
path = os.path.join(alternate_location, candidate)
if os.path.isdir(location):
for candidate in os.listdir(location):
if candidate.lower() == filename.lower():
return os.path.join(location, candidate)
path = os.path.join(location, candidate)
if path:
try:
ReventParser.check_revent_file(path)
return path
except ValueError as e:
self.logger.warning(e.message)
else:
raise ValueError('Unexpected resource type: {}'.format(resource.name))

78
wlauto/utils/revent.py Normal file
View File

@ -0,0 +1,78 @@
# Copyright 2016 ARM Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import struct
import datetime
import os
class ReventParser(object):
"""
Parses revent binary recording files so they can be easily read within python.
"""
int32_struct = struct.Struct("<i")
header_struct = struct.Struct("<6sH")
event_struct = struct.Struct("<i4xqqHHi")
def __init__(self):
self.path = None
self.device_paths = []
def parse(self, path):
ReventParser.check_revent_file(path)
with open(path, "rb") as f:
_read_struct(f, ReventParser.header_struct)
path_count, = _read_struct(f, self.int32_struct)
for _ in xrange(path_count):
path_length, = _read_struct(f, self.int32_struct)
if path_length >= 30:
raise ValueError("path length too long. corrupt file")
self.device_paths.append(f.read(path_length))
while f.tell() < os.path.getsize(path):
device_id, sec, usec, typ, code, value = _read_struct(f, self.event_struct)
yield (device_id, datetime.datetime.fromtimestamp(sec + float(usec) / 1000000),
typ, code, value)
@staticmethod
def check_revent_file(path):
"""
Checks whether a file starts with "REVENT"
"""
with open(path, "rb") as f:
magic, file_version = _read_struct(f, ReventParser.header_struct)
if magic != "REVENT":
msg = "'{}' isn't an revent file, are you using an old recording?"
raise ValueError(msg.format(path))
return file_version
@staticmethod
def get_revent_duration(path):
"""
Takes an ReventParser and returns the duration of the revent recording in seconds.
"""
revent_parser = ReventParser().parse(path)
first = last = next(revent_parser)
for last in revent_parser:
pass
return (last[1] - first[1]).total_seconds()
def _read_struct(f, struct_spec):
data = f.read(struct_spec.size)
return struct_spec.unpack(data)