|  | # Copyright 2019 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. | 
|  |  | 
|  | """Implements commands for running/interacting with Fuchsia on an emulator.""" | 
|  |  | 
|  | import amber_repo | 
|  | import boot_data | 
|  | import logging | 
|  | import os | 
|  | import runner_logs | 
|  | import subprocess | 
|  | import sys | 
|  | import target | 
|  | import tempfile | 
|  |  | 
|  |  | 
|  | class EmuTarget(target.Target): | 
|  | def __init__(self, out_dir, target_cpu, system_log_file, fuchsia_out_dir): | 
|  | """out_dir: The directory which will contain the files that are | 
|  | generated to support the emulator deployment. | 
|  | target_cpu: The emulated target CPU architecture. | 
|  | Can be 'x64' or 'arm64'.""" | 
|  |  | 
|  | # fuchsia_out_dir is unused by emulator targets. | 
|  | del fuchsia_out_dir | 
|  |  | 
|  | super(EmuTarget, self).__init__(out_dir, target_cpu) | 
|  | self._emu_process = None | 
|  | self._system_log_file = system_log_file | 
|  | self._amber_repo = None | 
|  |  | 
|  | def __enter__(self): | 
|  | return self | 
|  |  | 
|  | def _BuildCommand(self): | 
|  | """Build the command that will be run to start Fuchsia in the emulator.""" | 
|  | pass | 
|  |  | 
|  | def _SetEnv(self): | 
|  | return os.environ.copy() | 
|  |  | 
|  | # Used by the context manager to ensure that the emulator is killed when | 
|  | # the Python process exits. | 
|  | def __exit__(self, exc_type, exc_val, exc_tb): | 
|  | self.Shutdown(); | 
|  |  | 
|  | def Start(self): | 
|  | emu_command = self._BuildCommand() | 
|  |  | 
|  | # We pass a separate stdin stream. Sharing stdin across processes | 
|  | # leads to flakiness due to the OS prematurely killing the stream and the | 
|  | # Python script panicking and aborting. | 
|  | # The precise root cause is still nebulous, but this fix works. | 
|  | # See crbug.com/741194. | 
|  | logging.debug('Launching %s.' % (self.EMULATOR_NAME)) | 
|  | logging.debug(' '.join(emu_command)) | 
|  |  | 
|  | # Zircon sends debug logs to serial port (see kernel.serial=legacy flag | 
|  | # above). Serial port is redirected to a file through emulator stdout. | 
|  | # If runner_logs are not enabled, we output the kernel serial log | 
|  | # to a temporary file, and print that out if we are unable to connect to | 
|  | # the emulator guest, to make it easier to diagnose connectivity issues. | 
|  | temporary_log_file = None | 
|  | if runner_logs.IsEnabled(): | 
|  | stdout = runner_logs.FileStreamFor('serial_log') | 
|  | else: | 
|  | temporary_log_file = tempfile.NamedTemporaryFile('w') | 
|  | stdout = temporary_log_file | 
|  |  | 
|  | # TODO(crbug.com/1100402): Delete when no longer needed for debug info. | 
|  | # Log system statistics at the start of the emulator run. | 
|  | _LogSystemStatistics('system_start_statistics_log') | 
|  |  | 
|  | self._emu_process = subprocess.Popen(emu_command, | 
|  | stdin=open(os.devnull), | 
|  | stdout=stdout, | 
|  | stderr=subprocess.STDOUT, | 
|  | env=self._SetEnv()) | 
|  |  | 
|  | try: | 
|  | self._WaitUntilReady() | 
|  | except target.FuchsiaTargetException: | 
|  | if temporary_log_file: | 
|  | logging.info('Kernel logs:\n' + | 
|  | open(temporary_log_file.name, 'r').read()) | 
|  | raise | 
|  |  | 
|  | def GetAmberRepo(self): | 
|  | if not self._amber_repo: | 
|  | self._amber_repo = amber_repo.ManagedAmberRepo(self) | 
|  |  | 
|  | return self._amber_repo | 
|  |  | 
|  | def Shutdown(self): | 
|  | if not self._emu_process: | 
|  | logging.error('%s did not start' % (self.EMULATOR_NAME)) | 
|  | return | 
|  | returncode = self._emu_process.poll() | 
|  | if returncode == None: | 
|  | logging.info('Shutting down %s' % (self.EMULATOR_NAME)) | 
|  | self._emu_process.kill() | 
|  | elif returncode == 0: | 
|  | logging.info('%s quit unexpectedly without errors' % self.EMULATOR_NAME) | 
|  | elif returncode < 0: | 
|  | logging.error('%s was terminated by signal %d' % | 
|  | (self.EMULATOR_NAME, -returncode)) | 
|  | else: | 
|  | logging.error('%s quit unexpectedly with exit code %d' % | 
|  | (self.EMULATOR_NAME, returncode)) | 
|  |  | 
|  | # TODO(crbug.com/1100402): Delete when no longer needed for debug info. | 
|  | # Log system statistics at the end of the emulator run. | 
|  | _LogSystemStatistics('system_end_statistics_log') | 
|  |  | 
|  |  | 
|  | def _IsEmuStillRunning(self): | 
|  | if not self._emu_process: | 
|  | return False | 
|  | return os.waitpid(self._emu_process.pid, os.WNOHANG)[0] == 0 | 
|  |  | 
|  | def _GetEndpoint(self): | 
|  | if not self._IsEmuStillRunning(): | 
|  | raise Exception('%s quit unexpectedly.' % (self.EMULATOR_NAME)) | 
|  | return ('localhost', self._host_ssh_port) | 
|  |  | 
|  | def _GetSshConfigPath(self): | 
|  | return boot_data.GetSSHConfigPath(self._out_dir) | 
|  |  | 
|  |  | 
|  | # TODO(crbug.com/1100402): Delete when no longer needed for debug info. | 
|  | def _LogSystemStatistics(log_file_name): | 
|  | statistics_log = runner_logs.FileStreamFor(log_file_name) | 
|  | # Log the cpu load and process information. | 
|  | subprocess.call(['top', '-b', '-n', '1'], | 
|  | stdin=open(os.devnull), | 
|  | stdout=statistics_log, | 
|  | stderr=subprocess.STDOUT) | 
|  | subprocess.call(['ps', '-ax'], | 
|  | stdin=open(os.devnull), | 
|  | stdout=statistics_log, | 
|  | stderr=subprocess.STDOUT) |