| # 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) |