| #!/usr/bin/env python |
| # 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. |
| |
| import os |
| import sys |
| |
| # We should remove the skypy dependencies from this script. |
| sys.path.append(os.path.abspath(os.path.join(__file__, '../../../sky/tools'))) |
| |
| from skypy.skyserver import SkyServer |
| import argparse |
| import json |
| import logging |
| import pipes |
| import re |
| import requests |
| import signal |
| import skypy.paths |
| import StringIO |
| import subprocess |
| import time |
| import urlparse |
| import platform |
| |
| SUPPORTED_MIME_TYPES = [ |
| 'text/html', |
| 'text/sky', |
| 'application/dart', |
| ] |
| |
| DEFAULT_SKY_COMMAND_PORT = 7777 |
| GDB_PORT = 8888 |
| SKY_SERVER_PORT = 9999 |
| DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/examples/home.sky" |
| |
| ANDROID_PACKAGE = "org.chromium.mojo.shell" |
| ANDROID_ACTIVITY = "%s/.MojoShellActivity" % ANDROID_PACKAGE |
| ANDROID_APK_NAME = 'MojoShell.apk' |
| |
| PID_FILE_PATH = "/tmp/mojodb.pids" |
| CACHE_LINKS_PATH = '/tmp/mojo_cache_links' |
| |
| SRC_ROOT = skypy.paths.Paths('ignored').src_root |
| ADB_PATH = os.path.join(SRC_ROOT, |
| 'third_party/android_tools/sdk/platform-tools/adb') |
| |
| # TODO(iansf): Fix undefined behavior when you have more than one device attached. |
| SYSTEM_LIBS_ROOT_PATH = '/tmp/device_libs/%s' % (subprocess.check_output([ADB_PATH, 'get-serialno']).strip()) |
| |
| |
| # FIXME: Move this into mopy.config |
| def gn_args_from_build_dir(build_dir): |
| gn_cmd = [ |
| 'gn', 'args', |
| build_dir, |
| '--list', '--short' |
| ] |
| config = {} |
| for line in subprocess.check_output(gn_cmd).strip().split('\n'): |
| # FIXME: This doesn't handle = in values. |
| key, value = line.split(' = ') |
| config[key] = value |
| return config |
| |
| |
| class SkyDebugger(object): |
| def __init__(self): |
| self.pids = {} |
| self.paths = None |
| |
| def _server_root_for_url(self, url_or_path): |
| path = os.path.abspath(url_or_path) |
| if os.path.commonprefix([path, SRC_ROOT]) == SRC_ROOT: |
| server_root = SRC_ROOT |
| else: |
| server_root = os.path.dirname(path) |
| logging.warn( |
| '%s is outside of mojo root, using %s as server root' % |
| (path, server_root)) |
| return server_root |
| |
| def _in_chromoting(self): |
| return os.environ.get('CHROME_REMOTE_DESKTOP_SESSION', False) |
| |
| def _wrap_for_android(self, shell_args): |
| # am shell --esa: (someone shoot me now) |
| # [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]] |
| # (to embed a comma into a string escape it using "\,") |
| escaped_args = map(lambda arg: arg.replace(',', '\\,'), shell_args) |
| return [ |
| ADB_PATH, 'shell', |
| 'am', 'start', |
| '-W', |
| '-S', |
| '-a', 'android.intent.action.VIEW', |
| '-n', ANDROID_ACTIVITY, |
| # FIXME: This quoting is very error-prone. Perhaps we should read |
| # our args from a file instead? |
| '--esa', 'parameters', ','.join(escaped_args), |
| ] |
| |
| def _build_mojo_shell_command(self, args, is_android): |
| content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer') |
| for mime_type in SUPPORTED_MIME_TYPES] |
| |
| remote_command_port = self.pids.get('remote_sky_command_port', self.pids['sky_command_port']) |
| remote_server_port = self.pids.get('remote_sky_server_port', self.pids['sky_server_port']) |
| |
| shell_args = [ |
| '--v=1', |
| '--content-handlers=%s' % ','.join(content_handlers), |
| '--url-mappings=mojo:window_manager=mojo:kiosk_wm', |
| '--args-for=mojo:debugger %d --wm' % remote_command_port, |
| 'mojo:debugger', |
| ] |
| |
| if args.url_or_path: |
| shell_args.append( |
| '--args-for=mojo:window_manager %s' % self._url_from_args(args)) |
| |
| if args.trace_startup: |
| shell_args.append('--trace-startup') |
| |
| # Map all mojo: urls to http: urls using the --origin command. |
| build_dir_url = SkyServer.url_for_path( |
| remote_server_port, |
| self.pids['sky_server_root'], |
| self.pids['build_dir']) |
| |
| # TODO(eseidel): We should do this on linux, but we need to fix |
| # mojo http loading to be faster first. |
| if is_android: |
| shell_args += ['--origin=%s' % build_dir_url] |
| |
| # Desktop-only work-around for mojo crashing under chromoting. |
| if not is_android and args.use_osmesa: |
| shell_args.append( |
| '--args-for=mojo:native_viewport_service --use-osmesa') |
| |
| if is_android and args.gdb: |
| shell_args.append('--wait-for-debugger') |
| shell_args.append('--predictable-app-filenames') |
| |
| if 'remote_sky_server_port' in self.pids: |
| shell_command = self._wrap_for_android(shell_args) |
| else: |
| shell_command = [self.paths.mojo_shell_path] + shell_args |
| |
| return shell_command |
| |
| def sky_server_for_args(self, args, packages_root): |
| # FIXME: This is a hack. sky_server should just take a build_dir |
| # not a magical "configuration" name. |
| configuration = os.path.basename(os.path.normpath(self.paths.build_dir)) |
| server_root = self._server_root_for_url(args.url_or_path) |
| return SkyServer(SKY_SERVER_PORT, server_root, packages_root) |
| |
| def _create_paths_for_build_dir(self, build_dir): |
| # skypy.paths.Paths takes a root-relative build_dir argument. :( |
| abs_build_dir = os.path.abspath(build_dir) |
| root_relative_build_dir = os.path.relpath(abs_build_dir, SRC_ROOT) |
| return skypy.paths.Paths(root_relative_build_dir) |
| |
| def _find_remote_pid_for_package(self, package): |
| ps_output = subprocess.check_output([ADB_PATH, 'shell', 'ps']) |
| for line in ps_output.split('\n'): |
| fields = line.split() |
| if fields and fields[-1] == package: |
| return fields[1] |
| return None |
| |
| def _find_install_location_for_package(self, package): |
| pm_command = [ADB_PATH, 'shell', 'pm', 'path', package] |
| pm_output = subprocess.check_output(pm_command) |
| # e.g. package:/data/app/org.chromium.mojo.shell-1/base.apk |
| return pm_output.split(':')[-1] |
| |
| def start_command(self, args): |
| # FIXME: Lame that we use self for a command-specific variable. |
| self.paths = self._create_paths_for_build_dir(args.build_dir) |
| self.stop_command(None) # Quit any existing process. |
| |
| # FIXME: This is probably not the right way to compute is_android |
| # from the build directory? |
| gn_args = gn_args_from_build_dir(self.paths.build_dir) |
| is_android = 'android_sdk_version' in gn_args |
| |
| shell_found = True |
| if is_android: |
| apk_path = os.path.join(self.paths.build_dir, 'apks', ANDROID_APK_NAME) |
| if not os.path.exists(apk_path): |
| print "%s not found in build_dir '%s'" % \ |
| (ANDROID_APK_NAME, os.path.join(args.build_dir, 'apks')) |
| shell_found = False |
| elif not os.path.exists(self.paths.mojo_shell_path): |
| print "mojo_shell not found in build_dir '%s'" % args.build_dir |
| shell_found = False |
| |
| if not shell_found: |
| print "Are you sure you sure that's a valid build_dir location?" |
| print "See mojodb start --help for more info" |
| sys.exit(2) |
| |
| if is_android and args.gdb and not 'is_debug' in gn_args: |
| # FIXME: We don't include gdbserver in the release APK... |
| print "Cannot debug Release builds on Android" |
| sys.exit(2) |
| |
| dart_pkg_dir = os.path.join(self.paths.build_dir, 'gen', 'dart-pkg') |
| packages_root = os.path.join(dart_pkg_dir, 'packages') |
| |
| sky_server = self.sky_server_for_args(args, packages_root) |
| self.pids['sky_server_pid'] = sky_server.start() |
| self.pids['sky_server_port'] = sky_server.port |
| self.pids['sky_server_root'] = sky_server.root |
| |
| self.pids['build_dir'] = self.paths.build_dir |
| self.pids['sky_command_port'] = args.command_port |
| |
| if is_android: |
| # TODO(eseidel): This should move into a helper method and handle |
| # failures with nice messages explaining how to get root. |
| subprocess.check_call([ADB_PATH, 'root']) |
| |
| # We could make installing conditional on an argument. |
| # -r to replace an existing apk, -d to allow version downgrade. |
| subprocess.check_call([ADB_PATH, 'install', '-r', '-d', apk_path]) |
| |
| port_string = 'tcp:%s' % sky_server.port |
| subprocess.check_call([ |
| ADB_PATH, 'reverse', port_string, port_string |
| ]) |
| self.pids['remote_sky_server_port'] = sky_server.port |
| |
| port_string = 'tcp:%s' % args.command_port |
| subprocess.check_call([ |
| ADB_PATH, 'forward', port_string, port_string |
| ]) |
| self.pids['remote_sky_command_port'] = args.command_port |
| |
| shell_command = self._build_mojo_shell_command(args, is_android) |
| |
| # On android we can't launch inside gdb, but rather have to attach. |
| if not is_android and args.gdb: |
| shell_command = ['gdbserver', ':%d' % GDB_PORT] + shell_command |
| |
| print ' '.join(map(pipes.quote, shell_command)) |
| # This pid is meaningless on android (it's the adb shell pid) |
| start_command_pid = subprocess.Popen(shell_command).pid |
| |
| if is_android: |
| # TODO(eseidel): am start -W does not seem to work? |
| pid_tries = 0 |
| while True: |
| pid = self._find_remote_pid_for_package(ANDROID_PACKAGE) |
| if pid or pid_tries > 3: |
| break |
| logging.debug('No pid for %s yet, waiting' % ANDROID_PACKAGE) |
| time.sleep(5) |
| pid_tries += 1 |
| |
| if not pid: |
| logging.error('Failed to find mojo_shell pid on device!') |
| return |
| self.pids['mojo_shell_pid'] = pid |
| else: |
| self.pids['mojo_shell_pid'] = start_command_pid |
| |
| if args.gdb and is_android: |
| # We push our own copy of gdbserver with the package since |
| # the default gdbserver is a different version from our gdb. |
| package_path = \ |
| self._find_install_location_for_package(ANDROID_PACKAGE) |
| gdb_server_path = os.path.join( |
| os.path.dirname(package_path), 'lib/arm/gdbserver') |
| gdbserver_cmd = [ |
| ADB_PATH, 'shell', |
| gdb_server_path, '--attach', |
| ':%d' % GDB_PORT, |
| str(self.pids['mojo_shell_pid']) |
| ] |
| print ' '.join(map(pipes.quote, gdbserver_cmd)) |
| self.pids['adb_shell_gdbserver_pid'] = \ |
| subprocess.Popen(gdbserver_cmd).pid |
| |
| port_string = 'tcp:%d' % GDB_PORT |
| subprocess.check_call([ |
| ADB_PATH, 'forward', port_string, port_string |
| ]) |
| self.pids['remote_gdbserver_port'] = GDB_PORT |
| |
| if not args.gdb: |
| if not self._wait_for_sky_command_port(): |
| logging.error('Failed to start sky') |
| self.stop_command(None) |
| else: |
| # We could just run gdb_attach_command here, but when I do that |
| # it auto-suspends in my zsh. Unclear why. |
| # self.gdb_attach_command(args) |
| print "Run 'mojodb gdb_attach' to attach." |
| |
| def _kill_if_exists(self, key, name): |
| pid = self.pids.pop(key, None) |
| if not pid: |
| logging.info('No pid for %s, nothing to do.' % name) |
| return |
| logging.info('Killing %s (%d).' % (name, pid)) |
| try: |
| os.kill(pid, signal.SIGTERM) |
| except OSError: |
| logging.info('%s (%d) already gone.' % (name, pid)) |
| |
| def stop_command(self, args): |
| # TODO(eseidel): mojo_shell crashes when attempting graceful shutdown. |
| # self._run_basic_command('/quit') |
| |
| self._kill_if_exists('sky_server_pid', 'sky_server') |
| |
| if 'remote_sky_server_port' in self.pids: |
| port_string = 'tcp:%s' % self.pids['remote_sky_server_port'] |
| subprocess.call([ADB_PATH, 'reverse', '--remove', port_string]) |
| |
| if 'remote_sky_command_port' in self.pids: |
| # adb forward --remove takes the *host* port, not the remote port. |
| port_string = 'tcp:%s' % self.pids['sky_command_port'] |
| subprocess.call([ADB_PATH, 'forward', '--remove', port_string]) |
| |
| subprocess.call([ |
| ADB_PATH, 'shell', 'am', 'force-stop', ANDROID_PACKAGE]) |
| else: |
| # Only try to kill mojo_shell if it's running locally. |
| self._kill_if_exists('mojo_shell_pid', 'mojo_shell') |
| |
| if 'remote_gdbserver_port' in self.pids: |
| self._kill_if_exists('adb_shell_gdbserver_pid', |
| 'adb shell gdbserver') |
| |
| port_string = 'tcp:%s' % self.pids['remote_gdbserver_port'] |
| subprocess.call([ADB_PATH, 'forward', '--remove', port_string]) |
| self.pids = {} # Clear out our pid file. |
| |
| def _url_from_args(self, args): |
| if urlparse.urlparse(args.url_or_path).scheme: |
| return args.url_or_path |
| # The load happens on the remote device, use the remote port. |
| remote_sky_server_port = self.pids.get('remote_sky_server_port', |
| self.pids['sky_server_port']) |
| return SkyServer.url_for_path(remote_sky_server_port, |
| self.pids['sky_server_root'], args.url_or_path) |
| |
| def load_command(self, args): |
| self._run_basic_command('/load', self._url_from_args(args)) |
| |
| def _read_mojo_map(self): |
| # TODO(eseidel): Does not work for android. |
| mojo_map_path = "/tmp/mojo_shell.%d.maps" % self.pids['mojo_shell_pid'] |
| with open(mojo_map_path, 'r') as maps_file: |
| lines = maps_file.read().strip().split('\n') |
| return dict(map(lambda line: line.split(' '), lines)) |
| |
| def stop_tracing_command(self, args): |
| file_name = args.file_name |
| trace = self._send_command_to_sky('/stop_tracing').content |
| with open(file_name, "wb") as trace_file: |
| trace_file.write('{"traceEvents":[') |
| trace_file.write(trace) |
| trace_file.write(']}') |
| print "Trace saved in %s" % file_name |
| |
| def stop_profiling_command(self, args): |
| self._run_basic_command('/stop_profiling') |
| mojo_map = self._read_mojo_map() |
| |
| # TODO(eseidel): We should have a helper for resolving urls, etc. |
| remote_server_port = self.pids.get('remote_sky_server_port', self.pids['sky_server_port']) |
| build_dir_url = SkyServer.url_for_path( |
| remote_server_port, |
| self.pids['sky_server_root'], |
| self.pids['build_dir']) |
| |
| # Map /tmp cache paths to urls and then to local build_dir paths. |
| def map_to_local_paths(match): |
| path = match.group('mojo_path') |
| url = mojo_map.get(path) |
| if url and url.startswith(build_dir_url): |
| return url.replace(build_dir_url, self.pids['build_dir']) |
| return match.group(0) |
| |
| MOJO_PATH_RE = re.compile(r'(?P<mojo_path>\S+\.mojo)') |
| MOJO_NAME_RE = re.compile(r'(?P<mojo_name>\w+)\.mojo') |
| |
| with open("sky_viewer.pprof", "rb+") as profile_file: |
| # ISO-8859-1 can represent arbitrary binary while still keeping |
| # ASCII characters in the ASCII range (allowing us to regexp). |
| # http://en.wikipedia.org/wiki/ISO/IEC_8859-1 |
| as_string = profile_file.read().decode('iso-8859-1') |
| # Using the mojo_shell.PID.maps file tmp paths to build_dir paths. |
| as_string = MOJO_PATH_RE.sub(map_to_local_paths, as_string) |
| # In release foo.mojo is stripped but libfoo_library.so isn't. |
| as_string = MOJO_NAME_RE.sub(r'lib\1_library.so', as_string) |
| profile_file.seek(0) |
| profile_file.write(as_string.encode('iso-8859-1')) |
| profile_file.truncate() |
| |
| def _command_base_url(self): |
| return 'http://localhost:%s' % self.pids['sky_command_port'] |
| |
| def _send_command_to_sky(self, command_path, payload=None): |
| url = 'http://localhost:%s%s' % ( |
| self.pids['sky_command_port'], command_path) |
| if payload: |
| response = requests.post(url, payload) |
| else: |
| response = requests.get(url) |
| return response |
| |
| def _run_basic_command(self, command_path, payload=None): |
| print self._send_command_to_sky(command_path, payload=payload).text |
| |
| # FIXME: These could be made into a context object with __enter__/__exit__. |
| def _load_pid_file(self, path): |
| try: |
| with open(path, 'r') as pid_file: |
| return json.load(pid_file) |
| except: |
| if os.path.exists(path): |
| logging.warn('Failed to read pid file: %s' % path) |
| return {} |
| |
| def _write_pid_file(self, path, pids): |
| try: |
| with open(path, 'w') as pid_file: |
| json.dump(pids, pid_file, indent=2, sort_keys=True) |
| except: |
| logging.warn('Failed to write pid file: %s' % path) |
| |
| def _add_basic_command(self, subparsers, name, url_path, help_text): |
| parser = subparsers.add_parser(name, help=help_text) |
| command = lambda args: self._run_basic_command(url_path) |
| parser.set_defaults(func=command) |
| |
| def _wait_for_sky_command_port(self): |
| tries = 0 |
| while True: |
| try: |
| self._run_basic_command('/') |
| return True |
| except: |
| tries += 1 |
| if tries == 3: |
| logging.warn('Still waiting for sky on port %s' % |
| self.pids['sky_command_port']) |
| if tries > 10: |
| return False |
| time.sleep(1) |
| |
| def logcat_command(self, args): |
| TAGS = [ |
| 'AndroidHandler', |
| 'MojoMain', |
| 'MojoShellActivity', |
| 'MojoShellApplication', |
| 'chromium', |
| ] |
| subprocess.call([ADB_PATH, 'logcat', '-d', '-s'] + TAGS) |
| |
| def _pull_system_libraries(self, system_libs_root): |
| # Pull down the system libraries this pid has already mapped in. |
| # TODO(eseidel): This does not handle dynamic loads. |
| library_cacher_path = os.path.join( |
| self.paths.sky_tools_directory, 'android_library_cacher.py') |
| subprocess.call([ |
| library_cacher_path, system_libs_root, self.pids['mojo_shell_pid'] |
| ]) |
| |
| # TODO(eseidel): adb_gdb does, this, unclear why solib-absolute-prefix |
| # doesn't make this explicit listing not necessary? |
| return subprocess.check_output([ |
| 'find', system_libs_root, |
| '-mindepth', '1', |
| '-maxdepth', '4', |
| '-type', 'd', |
| ]).strip().split('\n') |
| |
| def _add_android_library_links(self, links_path): |
| # TODO(eseidel): This might not match mojo_shell on the device? |
| # TODO(eseidel): Should we pass libmojo_shell.so as 'file' to gdb? |
| shell_link_path = os.path.join(links_path, 'libmojo_shell.so') |
| if os.path.lexists(shell_link_path): |
| os.unlink(shell_link_path) |
| os.symlink(self.paths.mojo_shell_path, shell_link_path) |
| |
| def gdb_attach_command(self, args): |
| self.paths = self._create_paths_for_build_dir(self.pids['build_dir']) |
| |
| if not os.path.exists(CACHE_LINKS_PATH): |
| os.makedirs(CACHE_LINKS_PATH) |
| cache_linker_path = os.path.join( |
| self.paths.sky_tools_directory, 'mojo_cache_linker.py') |
| subprocess.check_call([ |
| cache_linker_path, CACHE_LINKS_PATH, self.paths.build_dir]) |
| |
| symbol_search_paths = [ |
| self.pids['build_dir'], |
| CACHE_LINKS_PATH, |
| ] |
| gdb_path = '/usr/bin/gdb' |
| |
| eval_commands = [ |
| 'directory %s' % self.paths.src_root, |
| 'file %s' % self.paths.mojo_shell_path, |
| 'target remote localhost:%s' % GDB_PORT, |
| ] |
| |
| # A bunch of extra work is needed for android: |
| if 'remote_sky_server_port' in self.pids: |
| self._add_android_library_links(CACHE_LINKS_PATH) |
| |
| system_lib_dirs = self._pull_system_libraries(SYSTEM_LIBS_ROOT_PATH) |
| eval_commands.append( |
| 'set solib-absolute-prefix %s' % SYSTEM_LIBS_ROOT_PATH) |
| |
| symbol_search_paths = system_lib_dirs + symbol_search_paths |
| |
| # TODO(eseidel): We need to look up the toolchain somehow? |
| if platform.system() == 'Darwin': |
| gdb_path = os.path.join(SRC_ROOT, 'third_party/android_tools/ndk/' |
| 'toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/' |
| 'bin/arm-linux-androideabi-gdb') |
| else: |
| gdb_path = os.path.join(SRC_ROOT, 'third_party/android_tools/ndk/' |
| 'toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/' |
| 'bin/arm-linux-androideabi-gdb') |
| |
| # Set solib-search-path after letting android modify symbol_search_paths |
| eval_commands.append( |
| 'set solib-search-path %s' % ':'.join(symbol_search_paths)) |
| |
| exec_command = [gdb_path] |
| for command in eval_commands: |
| exec_command += ['--eval-command', command] |
| |
| print " ".join(exec_command) |
| |
| # Write out our pid file before we exec ourselves. |
| self._write_pid_file(PID_FILE_PATH, self.pids) |
| |
| # Exec gdb directly to avoid python intercepting symbols, etc. |
| os.execv(exec_command[0], exec_command) |
| |
| def print_crash_command(self, args): |
| logcat_cmd = [ADB_PATH, 'logcat', '-d'] |
| logcat = subprocess.Popen(logcat_cmd, stdout=subprocess.PIPE) |
| |
| stack_path = os.path.join(SRC_ROOT, |
| 'mojo', 'devtools', 'common', 'android_stack_parser', 'stack') |
| stack = subprocess.Popen([stack_path, '-'], stdin=logcat.stdout) |
| logcat.wait() |
| stack.wait() |
| |
| def pids_command(self, args): |
| print json.dumps(self.pids, indent=1) |
| |
| def main(self): |
| logging.basicConfig(level=logging.WARNING) |
| logging.getLogger("requests").setLevel(logging.WARNING) |
| |
| self.pids = self._load_pid_file(PID_FILE_PATH) |
| |
| parser = argparse.ArgumentParser(description='Sky launcher/debugger') |
| subparsers = parser.add_subparsers(help='sub-command help') |
| |
| start_parser = subparsers.add_parser('start', |
| help='launch a new mojo_shell with sky') |
| start_parser.add_argument('--gdb', action='store_true') |
| start_parser.add_argument('--command-port', type=int, |
| default=DEFAULT_SKY_COMMAND_PORT) |
| start_parser.add_argument('--use-osmesa', action='store_true', |
| default=self._in_chromoting()) |
| start_parser.add_argument('build_dir', type=str) |
| start_parser.add_argument('url_or_path', nargs='?', type=str, |
| default=DEFAULT_URL) |
| start_parser.add_argument('--show-command', action='store_true', |
| help='Display the shell command and exit') |
| start_parser.add_argument('--trace-startup', action='store_true') |
| start_parser.set_defaults(func=self.start_command) |
| |
| stop_parser = subparsers.add_parser('stop', |
| help=('stop sky (as listed in %s)' % PID_FILE_PATH)) |
| stop_parser.set_defaults(func=self.stop_command) |
| |
| pids_parser = subparsers.add_parser('pids', |
| help='dump the current mojodb pids file') |
| pids_parser.set_defaults(func=self.pids_command) |
| |
| logcat_parser = subparsers.add_parser('logcat', |
| help=('dump sky-related logs from device')) |
| logcat_parser.set_defaults(func=self.logcat_command) |
| |
| print_crash_parser = subparsers.add_parser('print_crash', |
| help=('dump (and symbolicate) recent crash-stacks')) |
| print_crash_parser.set_defaults(func=self.print_crash_command) |
| |
| gdb_attach_parser = subparsers.add_parser('gdb_attach', |
| help='launch gdb and attach to gdbserver launched from start --gdb') |
| gdb_attach_parser.set_defaults(func=self.gdb_attach_command) |
| |
| self._add_basic_command(subparsers, 'start_tracing', '/start_tracing', |
| 'starts tracing the running sky instance') |
| self._add_basic_command(subparsers, 'reload', '/reload', |
| 'reload the current page') |
| self._add_basic_command(subparsers, 'start_profiling', '/start_profiling', |
| 'starts profiling the running sky instance (Linux only)') |
| |
| stop_tracing_parser = subparsers.add_parser('stop_tracing', |
| help='stops tracing the running sky instance') |
| stop_tracing_parser.add_argument('file_name', type=str, default='sky_viewer.trace') |
| stop_tracing_parser.set_defaults(func=self.stop_tracing_command) |
| |
| stop_profiling_parser = subparsers.add_parser('stop_profiling', |
| help='stops profiling the running sky instance (Linux only)') |
| stop_profiling_parser.set_defaults(func=self.stop_profiling_command) |
| |
| load_parser = subparsers.add_parser('load', |
| help='load a new page in the currently running sky') |
| load_parser.add_argument('url_or_path', type=str) |
| load_parser.set_defaults(func=self.load_command) |
| |
| args = parser.parse_args() |
| args.func(args) |
| |
| self._write_pid_file(PID_FILE_PATH, self.pids) |
| |
| |
| if __name__ == '__main__': |
| SkyDebugger().main() |