Index: tools/telemetry/telemetry/internal/backends/mandoline/android.py |
diff --git a/tools/telemetry/telemetry/internal/backends/mandoline/android.py b/tools/telemetry/telemetry/internal/backends/mandoline/android.py |
deleted file mode 100644 |
index 57fd4507812d501e1820205e3dd121a2a150cb70..0000000000000000000000000000000000000000 |
--- a/tools/telemetry/telemetry/internal/backends/mandoline/android.py |
+++ /dev/null |
@@ -1,278 +0,0 @@ |
-# Copyright 2014 The Chromium Authors. All rights reserved. |
-# Use of this source code is governed by a BSD-style license that can be |
-# found in the LICENSE file. |
- |
-# TODO(yzshen): Once the dep manager is ready, remove this file and use the one |
-# from src/mojo/tools directly. |
- |
- |
-import atexit |
-import logging |
-import os |
-import signal |
-import subprocess |
-import sys |
-import threading |
-import time |
- |
-from .paths import Paths |
- |
-from telemetry.internal.util import binary_manager |
- |
-from devil import base_error |
-from devil.android import apk_helper |
-from devil.android import device_errors |
-from devil.android import device_utils |
-from devil.android.sdk import adb_wrapper |
-from devil.constants import exit_codes |
- |
- |
-# Tags used by the mojo shell application logs. |
-LOGCAT_TAGS = [ |
- 'AndroidHandler', |
- 'MojoFileHelper', |
- 'MojoMain', |
- 'MojoShellActivity', |
- 'MojoShellApplication', |
- 'chromium', |
-] |
- |
-MAPPING_PREFIX = '--map-origin=' |
- |
- |
-def _ExitIfNeeded(process): |
- '''Exits |process| if it is still alive.''' |
- if process.poll() is None: |
- process.kill() |
- |
- |
-class AndroidShell(object): |
- ''' |
- Used to set up and run a given mojo shell binary on an Android device. |
- |config| is the mopy.config.Config for the build. |
- ''' |
- def __init__(self, config, chrome_root): |
- self.adb_path = adb_wrapper.AdbWrapper.GetAdbPath() |
- self.config = config |
- self.paths = Paths(config, chrome_root) |
- self.device = None |
- self.shell_args = [] |
- self.target_package = apk_helper.GetPackageName(self.paths.apk_path) |
- self.temp_gdb_dir = None |
- |
- # TODO(msw): Use devil's adb_wrapper and device_utils instead. |
- def _CreateADBCommand(self, args): |
- adb_command = [self.adb_path, '-s', self.device.adb.GetDeviceSerial()] |
- adb_command.extend(args) |
- logging.getLogger().debug('Command: %s', ' '.join(adb_command)) |
- return adb_command |
- |
- def _ReadFifo(self, path, pipe, on_fifo_closed, max_attempts=5): |
- ''' |
- Reads the fifo at |path| on the device and write the contents to |pipe|. |
- Calls |on_fifo_closed| when the fifo is closed. This method will try to find |
- the path up to |max_attempts|, waiting 1 second between each attempt. If it |
- cannot find |path|, a exception will be raised. |
- ''' |
- def Run(): |
- def _WaitForFifo(): |
- for _ in xrange(max_attempts): |
- if self.device.FileExists(path): |
- return |
- time.sleep(1) |
- on_fifo_closed() |
- raise Exception('Unable to find fifo: %s' % path) |
- _WaitForFifo() |
- stdout_cat = subprocess.Popen(self._CreateADBCommand([ |
- 'shell', |
- 'cat', |
- path]), |
- stdout=pipe) |
- atexit.register(_ExitIfNeeded, stdout_cat) |
- stdout_cat.wait() |
- on_fifo_closed() |
- |
- thread = threading.Thread(target=Run, name='StdoutRedirector') |
- thread.start() |
- |
- |
- def InitShell(self, device=None): |
- ''' |
- Runs adb as root, and installs the apk as needed. |device| is the target |
- device to run on, if multiple devices are connected. Returns 0 on success or |
- a non-zero exit code on a terminal failure. |
- ''' |
- try: |
- devices = device_utils.DeviceUtils.HealthyDevices() |
- if device: |
- self.device = next((d for d in devices if d == device), None) |
- if not self.device: |
- raise device_errors.DeviceUnreachableError(device) |
- elif devices: |
- self.device = devices[0] |
- else: |
- raise device_errors.NoDevicesError() |
- |
- logging.getLogger().debug('Using device: %s', self.device) |
- # Clean the logs on the device to avoid displaying prior activity. |
- self.device.adb.Logcat(clear=True) |
- self.device.EnableRoot() |
- self.device.Install(self.paths.apk_path) |
- except base_error.BaseError as e: |
- # Report 'device not found' as infra failures. See http://crbug.com/493900 |
- print 'Exception in AndroidShell.InitShell:\n%s' % str(e) |
- if e.is_infra_error or 'error: device not found' in str(e): |
- return exit_codes.INFRA |
- return exit_codes.ERROR |
- |
- return 0 |
- |
- def _GetProcessId(self, process): |
- '''Returns the process id of the process on the remote device.''' |
- while True: |
- line = process.stdout.readline() |
- pid_command = 'launcher waiting for GDB. pid: ' |
- index = line.find(pid_command) |
- if index != -1: |
- return line[index + len(pid_command):].strip() |
- return 0 |
- |
- def _GetLocalGdbPath(self): |
- '''Returns the path to the android gdb.''' |
- if self.config.target_cpu not in ('arm', 'x86', 'x64'): |
- raise Exception('Unknown target_cpu: %s' % self.config.target_cpu) |
- return binary_manager.FetchPath('gdb', self.config.target_cpu, 'android') |
- |
- def _WaitForProcessIdAndStartGdb(self, process): |
- ''' |
- Waits until we see the process id from the remote device, starts up |
- gdbserver on the remote device, and gdb on the local device. |
- ''' |
- # Wait until we see 'PID' |
- pid = self._GetProcessId(process) |
- assert pid != 0 |
- # No longer need the logcat process. |
- process.kill() |
- # Disable python's processing of SIGINT while running gdb. Otherwise |
- # control-c doesn't work well in gdb. |
- signal.signal(signal.SIGINT, signal.SIG_IGN) |
- gdbserver_process = subprocess.Popen(self._CreateADBCommand(['shell', |
- 'gdbserver', |
- '--attach', |
- ':5039', |
- pid])) |
- atexit.register(_ExitIfNeeded, gdbserver_process) |
- |
- gdbinit_path = os.path.join(self.temp_gdb_dir, 'gdbinit') |
- _CreateGdbInit(self.temp_gdb_dir, gdbinit_path, self.paths.build_dir) |
- |
- # Wait a second for gdb to start up on the device. Without this the local |
- # gdb starts before the remote side has registered the port. |
- # TODO(sky): maybe we should try a couple of times and then give up? |
- time.sleep(1) |
- |
- local_gdb_process = subprocess.Popen([self._GetLocalGdbPath(), |
- '-x', |
- gdbinit_path], |
- cwd=self.temp_gdb_dir) |
- atexit.register(_ExitIfNeeded, local_gdb_process) |
- local_gdb_process.wait() |
- signal.signal(signal.SIGINT, signal.SIG_DFL) |
- |
- def StartActivity(self, |
- activity_name, |
- arguments, |
- stdout, |
- on_fifo_closed, |
- temp_gdb_dir=None): |
- ''' |
- Starts the shell with the given |arguments|, directing output to |stdout|. |
- |on_fifo_closed| will be run if the FIFO can't be found or when it's closed. |
- |temp_gdb_dir| is set to a location with appropriate symlinks for gdb to |
- find when attached to the device's remote process on startup. |
- ''' |
- assert self.device |
- arguments += self.shell_args |
- |
- cmd = self._CreateADBCommand([ |
- 'shell', |
- 'am', |
- 'start', |
- '-S', |
- '-a', 'android.intent.action.VIEW', |
- '-n', '%s/%s.%s' % (self.target_package, |
- self.target_package, |
- activity_name)]) |
- |
- logcat_process = None |
- if temp_gdb_dir: |
- self.temp_gdb_dir = temp_gdb_dir |
- arguments.append('--wait-for-debugger') |
- # Remote debugging needs a port forwarded. |
- self.device.adb.Forward('tcp:5039', 'tcp:5039') |
- logcat_process = self.ShowLogs(stdout=subprocess.PIPE) |
- |
- fifo_path = '/data/data/%s/stdout.fifo' % self.target_package |
- subprocess.check_call(self._CreateADBCommand( |
- ['shell', 'rm', '-f', fifo_path])) |
- arguments.append('--fifo-path=%s' % fifo_path) |
- max_attempts = 200 if '--wait-for-debugger' in arguments else 5 |
- self._ReadFifo(fifo_path, stdout, on_fifo_closed, max_attempts) |
- |
- # Extract map-origin args and add the extras array with commas escaped. |
- parameters = [a for a in arguments if not a.startswith(MAPPING_PREFIX)] |
- parameters = [p.replace(',', r'\,') for p in parameters] |
- cmd += ['--esa', '%s.extras' % self.target_package, ','.join(parameters)] |
- |
- atexit.register(self.kill) |
- with open(os.devnull, 'w') as devnull: |
- cmd_process = subprocess.Popen(cmd, stdout=devnull) |
- if logcat_process: |
- self._WaitForProcessIdAndStartGdb(logcat_process) |
- cmd_process.wait() |
- |
- def kill(self): |
- '''Stops the mojo shell; matches the Popen.kill method signature.''' |
- self.device.ForceStop(self.target_package) |
- |
- def ShowLogs(self, stdout=sys.stdout): |
- '''Displays the mojo shell logs and returns the process reading the logs.''' |
- logcat = subprocess.Popen(self._CreateADBCommand([ |
- 'logcat', |
- '-s', |
- ' '.join(LOGCAT_TAGS)]), |
- stdout=stdout) |
- atexit.register(_ExitIfNeeded, logcat) |
- return logcat |
- |
- |
-def _CreateGdbInit(tmp_dir, gdb_init_path, build_dir): |
- ''' |
- Creates the gdbinit file. |
- |
- Args: |
- tmp_dir: the directory where the gdbinit and other files lives. |
- gdb_init_path: path to gdbinit |
- build_dir: path where build files are located. |
- ''' |
- gdbinit = ('target remote localhost:5039\n' |
- 'def reload-symbols\n' |
- ' set solib-search-path %s:%s\n' |
- 'end\n' |
- 'def info-symbols\n' |
- ' info sharedlibrary\n' |
- 'end\n' |
- 'reload-symbols\n' |
- 'echo \\n\\n' |
- 'You are now in gdb and need to type continue (or c) to continue ' |
- 'execution.\\n' |
- 'gdb is in the directory %s\\n' |
- 'The following functions have been defined:\\n' |
- 'reload-symbols: forces reloading symbols. If after a crash you\\n' |
- 'still do not see symbols you likely need to create a link in\\n' |
- 'the directory you are in.\\n' |
- 'info-symbols: shows status of current shared libraries.\\n' |
- 'NOTE: you may need to type reload-symbols again after a ' |
- 'crash.\\n\\n' % (tmp_dir, build_dir, tmp_dir)) |
- with open(gdb_init_path, 'w') as f: |
- f.write(gdbinit) |