123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- # 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 json
- import logging
- import os
- import subprocess
- import sys
- import tempfile
- import boot_data
- import common
- import ffx_session
- import pkg_repo
- import target
- class EmuTarget(target.Target):
- LOCAL_ADDRESS = 'localhost'
- def __init__(self, out_dir, target_cpu, logs_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'."""
- super(EmuTarget, self).__init__(out_dir, target_cpu, logs_dir)
- self._emu_process = None
- self._pkg_repo = None
- self._target_context = None
- self._ffx_target = 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()
- def Start(self):
- if common.IsRunningUnattended() and not self._HasNetworking():
- # Bots may accumulate stale manually-added targets with the same address
- # as the one to be added here. Preemtively remove any unknown targets at
- # this address before starting the emulator and adding it as a target.
- self._ffx_runner.remove_stale_targets('127.0.0.1')
- emu_command = self._BuildCommand()
- 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 self._log_manager.IsLoggingEnabled():
- stdout = self._log_manager.Open('serial_log')
- else:
- temporary_log_file = tempfile.NamedTemporaryFile('w')
- stdout = temporary_log_file
- self.LogProcessStatistics('proc_stat_start_log')
- self.LogSystemStatistics('system_statistics_start_log')
- self._emu_process = subprocess.Popen(emu_command,
- stdin=open(os.devnull),
- stdout=stdout,
- stderr=subprocess.STDOUT,
- env=self._SetEnv())
- try:
- self._ConnectToTarget()
- self.LogProcessStatistics('proc_stat_ready_log')
- except target.FuchsiaTargetException:
- self._DisconnectFromTarget()
- if temporary_log_file:
- logging.info('Kernel logs:\n' +
- open(temporary_log_file.name, 'r').read())
- raise
- def GetFfxTarget(self):
- assert self._ffx_target
- return self._ffx_target
- def Stop(self):
- try:
- self._DisconnectFromTarget()
- self._Shutdown()
- finally:
- self.LogProcessStatistics('proc_stat_end_log')
- self.LogSystemStatistics('system_statistics_end_log')
- super(EmuTarget, self).Stop()
- def GetPkgRepo(self):
- if not self._pkg_repo:
- self._pkg_repo = pkg_repo.ManagedPkgRepo(self)
- return self._pkg_repo
- def _Shutdown(self):
- """Shuts down the emulator."""
- raise NotImplementedError()
- def _HasNetworking(self):
- """Returns `True` if the emulator will be started with networking (e.g.,
- TUN/TAP emulated networking).
- """
- raise NotImplementedError()
- def _IsEmuStillRunning(self):
- """Returns `True` if the emulator is still running."""
- raise NotImplementedError()
- def _GetEndpoint(self):
- raise NotImplementedError()
- def _ConnectToTarget(self):
- with_network = self._HasNetworking()
- if not with_network:
- # The target was started without networking, so tell ffx how to find it.
- logging.info('Connecting to Fuchsia using ffx.')
- _, host_ssh_port = self._GetEndpoint()
- self._target_context = self._ffx_runner.scoped_target_context(
- '127.0.0.1', host_ssh_port)
- self._ffx_target = self._target_context.__enter__()
- self._ffx_target.wait(common.ATTACH_RETRY_SECONDS)
- super(EmuTarget, self)._ConnectToTarget()
- if with_network:
- # Interact with the target via its address:port, which ffx should now know
- # about.
- self._ffx_target = ffx_session.FfxTarget.from_address(
- self._ffx_runner, *self._GetEndpoint())
- def _DisconnectFromTarget(self):
- self._ffx_target = None
- if self._target_context:
- self._target_context.__exit__(None, None, None)
- self._target_context = None
- super(EmuTarget, self)._DisconnectFromTarget()
- def _GetSshConfigPath(self):
- return boot_data.GetSSHConfigPath()
- def LogSystemStatistics(self, log_file_name):
- self._LaunchSubprocessWithLogs(['top', '-b', '-n', '1'], log_file_name)
- self._LaunchSubprocessWithLogs(['ps', '-ax'], log_file_name)
- def LogProcessStatistics(self, log_file_name):
- self._LaunchSubprocessWithLogs(['cat', '/proc/stat'], log_file_name)
- def _LaunchSubprocessWithLogs(self, command, log_file_name):
- """Launch a subprocess and redirect stdout and stderr to log_file_name.
- Command will not be run if logging directory is not set."""
- if not self._log_manager.IsLoggingEnabled():
- return
- log = self._log_manager.Open(log_file_name)
- subprocess.call(command,
- stdin=open(os.devnull),
- stdout=log,
- stderr=subprocess.STDOUT)
|