| # Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| from mtedit import MTEdit |
| from mtlib.gesture_log import GestureLog |
| from mtlib.log import Log |
| from mtlib.platform import PlatformDatabase, PlatformProperties |
| from subprocess import Popen, PIPE, STDOUT |
| from tempfile import NamedTemporaryFile |
| from threading import Thread |
| import decimal |
| import json |
| import logging |
| import multiprocessing |
| import os |
| import sys |
| |
| default_log = logging.getLogger(__name__) |
| |
| def _GetAbsPath(path): |
| """ Return normalized path relative to this script """ |
| path = os.path.join(os.path.dirname(__file__), path) |
| return os.path.abspath(path) |
| |
| class ReplayResults(object): |
| def __init__(self, replay): |
| self.gestures_log = replay.gestures_log |
| self.evdev_log = replay.evdev_log |
| self.log = Log(activity=replay.activity_log) |
| self._gestures = None |
| |
| @property |
| def gestures(self): |
| if not self._gestures: |
| self._gestures = GestureLog(self.log.activity) |
| return self._gestures |
| |
| def View(self, what): |
| if what == None: |
| what = 'activity-log' |
| elif what == 'g': |
| what = 'gestures' |
| elif what == 'gl': |
| what = 'gestures-log' |
| elif what == 'el': |
| what = 'evdev-log' |
| elif what == 'al': |
| what = 'activity-log' |
| elif what == 'a': |
| what = 'activity' |
| elif what == 'e': |
| what = 'events' |
| |
| if what == 'gestures-log': |
| print self.gestures_log |
| elif what == 'evdev-log': |
| print self.evdev_log |
| elif what == 'activity-log': |
| print self.log.activity |
| elif what == 'activity': |
| MTEdit().View(self.log) |
| elif what == 'gestures': |
| for gesture in self.gestures.gestures: |
| print gesture |
| elif what == 'events': |
| for event in self.gestures.events: |
| print event |
| |
| |
| class MTReplay(object): |
| """ New API for replaying log files """ |
| def __init__(self): |
| self._database = None |
| |
| @property |
| def database(self): |
| if not self._database: |
| self._database = PlatformDatabase() |
| return self._database |
| |
| def Recompile(self, head=False): |
| def SafeExec(args, cwd): |
| default_log.info("Executing: %s", " ".join(args)) |
| process = Popen(args, cwd=cwd, stdout=PIPE, stderr=STDOUT) |
| ret = process.wait() |
| default_log.info("Process returned: %d", process.returncode) |
| if ret != 0: |
| print process.stdout.read() |
| sys.exit(-1) |
| |
| gestures_path = _GetAbsPath("../../gestures") |
| |
| if head: |
| SafeExec(['git', 'stash'], gestures_path) |
| |
| print "Recompiling gestures/libevdev/replay..." |
| SafeExec(["make", "-j", str(multiprocessing.cpu_count()), |
| "in-place"], _GetAbsPath("../")) |
| |
| if head: |
| SafeExec(['git', 'stash', 'pop'], gestures_path) |
| |
| def PlatformOf(self, log, debug=False): |
| return self.database.FindMatching(log, debug=debug) |
| |
| def Replay(self, log, override_properties=None, force_platform=None, |
| gdb=False, dbg_log=None): |
| dbg_log = dbg_log or default_log |
| if force_platform: |
| dbg_log.info("Forced platform: %s", force_platform) |
| platform = PlatformProperties(force_platform) |
| else: |
| dbg_log.info("Matching platforms...") |
| platform = self.PlatformOf(log) |
| if not platform: |
| dbg_log.info("Unable to find platform") |
| return None |
| dbg_log.info("Platform found: %s", platform.name) |
| |
| replay = RawReplay(platform.hwprops_file, platform.device_class) |
| |
| events = NamedTemporaryFile('w', delete=True) |
| events.write(log.evdev) |
| events.flush() |
| |
| properties = {} |
| properties.update(platform.properties) |
| if override_properties: |
| properties.update(override_properties) |
| |
| replay.Replay(events.name, json.dumps(properties), |
| debug=True, gdb_mode=gdb, dbg_log=dbg_log) |
| return ReplayResults(replay) |
| |
| def TrimEvdev(self, log, force_platform=None, gdb=False, dbg_log=None): |
| """ Trim evdev log to cover same range as activity log. """ |
| dbg_log = dbg_log or default_log |
| if force_platform: |
| dbg_log.info("Forced platform: %s", force_platform) |
| platform = PlatformProperties(force_platform) |
| else: |
| dbg_log.info("Matching platforms...") |
| platform = self.PlatformOf(log, False) |
| if not platform: |
| dbg_log.info("Unable to find platform") |
| return None |
| dbg_log.info("Platform found: %s", platform.name) |
| |
| replay = RawReplay(platform.hwprops_file, platform.device_class) |
| |
| # parse activity_log |
| decimal.setcontext(decimal.Context(prec=8)) |
| activity = json.loads(log.activity, parse_float=decimal.Decimal) |
| |
| # extract from/to times from activity log |
| hwstates = filter(lambda e: e["type"] == "hardwareState", |
| activity["entries"]) |
| if not hwstates: |
| dbg_log.warning("No HardwareStates generated") |
| return None |
| trim_from = hwstates[0]["timestamp"] |
| trim_to = hwstates[-1]["timestamp"] |
| dbg_log.info("Trimming from %f to %f", trim_from, trim_to) |
| |
| events = NamedTemporaryFile('w', delete=True) |
| events.write(log.evdev) |
| events.flush() |
| |
| trim_out = NamedTemporaryFile('r', delete=True) |
| |
| replay.Trim(events.name, trim_out.name, |
| trim_from=trim_from, trim_to=trim_to, |
| gdb_mode=gdb, dbg_log=dbg_log) |
| log.evdev = trim_out.read() |
| return log |
| |
| |
| class RawReplay(object): |
| """ High level interface to replay tool. |
| |
| It provides access to both the replay and the trim feature. |
| """ |
| |
| # default path for executable |
| _default_executable_path = _GetAbsPath('replay') |
| |
| def __init__(self, platform_file, device_class=None, |
| path_to_exe=_default_executable_path): |
| """ Create a new instance of the replay tool. |
| |
| The platform_file has to point to a file that contains the |
| simulated's device data. |
| """ |
| self._exe = path_to_exe |
| |
| # setup LD_LIBRARY_PATH for in-place installs |
| libevdev_path = _GetAbsPath('../../libevdev/in-place') |
| gestures_path = _GetAbsPath('../../gestures/in-place') |
| self._env = os.environ.copy() |
| self._env['LD_LIBRARY_PATH'] = '%s;%s' % (libevdev_path, gestures_path) |
| |
| self._platform_file = platform_file |
| self._device_class = device_class |
| self.evdev_log = None |
| self.gestures_log = None |
| self.activity_log = None |
| |
| def Trim(self, events_file, trim_out, trim_from=None, trim_to=None, |
| gdb_mode=False, dbg_log=None): |
| """ Shorthand for _execute for trimming. |
| |
| Stores trimed version of events_file in trim_out. |
| """ |
| self._Execute(events_file, None, False, False, False, |
| trim_out, trim_from, trim_to, gdb_mode=gdb_mode, dbg_log=dbg_log) |
| |
| def Replay(self, events_file, properties=None, debug=False, gdb_mode=False, |
| dbg_log=None): |
| """ Shorthand for _execute for replaying. |
| |
| debug=True/False enables or disables extra logs. |
| This method always returns the activity log as a string. |
| """ |
| self._Execute(events_file, properties, debug, debug, True, |
| gdb_mode=gdb_mode, dbg_log=dbg_log) |
| return self.activity_log |
| |
| def _Execute(self, events_file, properties=None, |
| log_evdev=True, log_gestures=True, log_activity=True, |
| trim_out=None, trim_from=None, trim_to=None, gdb_mode=False, |
| dbg_log=None): |
| """ Executes a replay process. |
| |
| The method arguments are translated to |
| command line arguments for the process. |
| This method throws an exception if the replay process returns and error. |
| |
| events_file: filename of file containing the event data |
| properties: string containing properties as a JSON object |
| log_evdev: enable logging of libevev. stored in self.evdev_log |
| log_gestures: enable logging of gestures. stored in self.gestures_log |
| log_evdev: enable generation of activity log in self.activity_log |
| trim_out: filename where to store result of trim operation |
| trim_from: timestamp of first SYN report to include |
| trim_to: timestamp of last SYN report to include |
| """ |
| # temporary files for program output |
| evdev_log_file = None |
| gestures_log_file = None |
| activity_log_file = None |
| working_dir = os.path.dirname(events_file) |
| |
| dbg_log = dbg_log or default_log |
| |
| # translate arguments to command line parameters |
| parameters = [self._exe] |
| parameters.extend(['--device', self._platform_file ]) |
| parameters.extend(['--events', events_file]) |
| |
| if self._device_class: |
| parameters.extend(['--class', self._device_class]) |
| |
| if log_evdev: |
| evdev_log_file = NamedTemporaryFile('r', delete=True) |
| parameters.extend(['--evdev-log', evdev_log_file.name]) |
| |
| if log_gestures: |
| gestures_log_file = NamedTemporaryFile('r', delete=True) |
| parameters.extend(['--gestures-log', gestures_log_file.name]) |
| |
| if log_activity: |
| activity_log_file = NamedTemporaryFile('r', delete=True) |
| parameters.extend(['--activity-log', activity_log_file.name]) |
| |
| if properties: |
| properties_file = NamedTemporaryFile('w', delete=True) |
| properties_file.write(properties) |
| properties_file.flush() |
| parameters.extend(['--properties', properties_file.name]) |
| |
| if trim_out: |
| parameters.extend(['--trim-out', trim_out]) |
| |
| if trim_from: |
| parameters.extend(['--trim-from', str(trim_from)]) |
| |
| if trim_to: |
| parameters.extend(['--trim-to', str(trim_to)]) |
| |
| # execute |
| if gdb_mode: |
| parameters = ['gdb', '--args'] + parameters |
| |
| dbg_log.info("Executing: %s", " ".join(parameters)) |
| process = Popen(parameters, env=self._env, cwd=working_dir) |
| process.wait() |
| dbg_log.info("Process returns: %d", process.returncode) |
| |
| # close temporary files |
| if log_evdev: |
| self.evdev_log = evdev_log_file.read() |
| evdev_log_file.close() |
| |
| if log_gestures: |
| self.gestures_log = gestures_log_file.read() |
| gestures_log_file.close() |
| |
| if log_activity: |
| self.activity_log = activity_log_file.read() |
| activity_log_file.close() |
| |
| if properties: |
| properties_file.close() |
| |
| if process.returncode != 0: |
| print 'Gestures Log: ' |
| print self.gestures_log |
| raise Exception('Process error: ', ' '.join(parameters)) |