emu_target.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. # Copyright 2019 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Implements commands for running/interacting with Fuchsia on an emulator."""
  5. import json
  6. import logging
  7. import os
  8. import subprocess
  9. import sys
  10. import tempfile
  11. import boot_data
  12. import common
  13. import ffx_session
  14. import pkg_repo
  15. import target
  16. class EmuTarget(target.Target):
  17. LOCAL_ADDRESS = 'localhost'
  18. def __init__(self, out_dir, target_cpu, logs_dir):
  19. """out_dir: The directory which will contain the files that are
  20. generated to support the emulator deployment.
  21. target_cpu: The emulated target CPU architecture.
  22. Can be 'x64' or 'arm64'."""
  23. super(EmuTarget, self).__init__(out_dir, target_cpu, logs_dir)
  24. self._emu_process = None
  25. self._pkg_repo = None
  26. self._target_context = None
  27. self._ffx_target = None
  28. def __enter__(self):
  29. return self
  30. def _BuildCommand(self):
  31. """Build the command that will be run to start Fuchsia in the emulator."""
  32. pass
  33. def _SetEnv(self):
  34. return os.environ.copy()
  35. def Start(self):
  36. if common.IsRunningUnattended() and not self._HasNetworking():
  37. # Bots may accumulate stale manually-added targets with the same address
  38. # as the one to be added here. Preemtively remove any unknown targets at
  39. # this address before starting the emulator and adding it as a target.
  40. self._ffx_runner.remove_stale_targets('127.0.0.1')
  41. emu_command = self._BuildCommand()
  42. logging.debug(' '.join(emu_command))
  43. # Zircon sends debug logs to serial port (see kernel.serial=legacy flag
  44. # above). Serial port is redirected to a file through emulator stdout.
  45. # If runner_logs are not enabled, we output the kernel serial log
  46. # to a temporary file, and print that out if we are unable to connect to
  47. # the emulator guest, to make it easier to diagnose connectivity issues.
  48. temporary_log_file = None
  49. if self._log_manager.IsLoggingEnabled():
  50. stdout = self._log_manager.Open('serial_log')
  51. else:
  52. temporary_log_file = tempfile.NamedTemporaryFile('w')
  53. stdout = temporary_log_file
  54. self.LogProcessStatistics('proc_stat_start_log')
  55. self.LogSystemStatistics('system_statistics_start_log')
  56. self._emu_process = subprocess.Popen(emu_command,
  57. stdin=open(os.devnull),
  58. stdout=stdout,
  59. stderr=subprocess.STDOUT,
  60. env=self._SetEnv())
  61. try:
  62. self._ConnectToTarget()
  63. self.LogProcessStatistics('proc_stat_ready_log')
  64. except target.FuchsiaTargetException:
  65. self._DisconnectFromTarget()
  66. if temporary_log_file:
  67. logging.info('Kernel logs:\n' +
  68. open(temporary_log_file.name, 'r').read())
  69. raise
  70. def GetFfxTarget(self):
  71. assert self._ffx_target
  72. return self._ffx_target
  73. def Stop(self):
  74. try:
  75. self._DisconnectFromTarget()
  76. self._Shutdown()
  77. finally:
  78. self.LogProcessStatistics('proc_stat_end_log')
  79. self.LogSystemStatistics('system_statistics_end_log')
  80. super(EmuTarget, self).Stop()
  81. def GetPkgRepo(self):
  82. if not self._pkg_repo:
  83. self._pkg_repo = pkg_repo.ManagedPkgRepo(self)
  84. return self._pkg_repo
  85. def _Shutdown(self):
  86. """Shuts down the emulator."""
  87. raise NotImplementedError()
  88. def _HasNetworking(self):
  89. """Returns `True` if the emulator will be started with networking (e.g.,
  90. TUN/TAP emulated networking).
  91. """
  92. raise NotImplementedError()
  93. def _IsEmuStillRunning(self):
  94. """Returns `True` if the emulator is still running."""
  95. raise NotImplementedError()
  96. def _GetEndpoint(self):
  97. raise NotImplementedError()
  98. def _ConnectToTarget(self):
  99. with_network = self._HasNetworking()
  100. if not with_network:
  101. # The target was started without networking, so tell ffx how to find it.
  102. logging.info('Connecting to Fuchsia using ffx.')
  103. _, host_ssh_port = self._GetEndpoint()
  104. self._target_context = self._ffx_runner.scoped_target_context(
  105. '127.0.0.1', host_ssh_port)
  106. self._ffx_target = self._target_context.__enter__()
  107. self._ffx_target.wait(common.ATTACH_RETRY_SECONDS)
  108. super(EmuTarget, self)._ConnectToTarget()
  109. if with_network:
  110. # Interact with the target via its address:port, which ffx should now know
  111. # about.
  112. self._ffx_target = ffx_session.FfxTarget.from_address(
  113. self._ffx_runner, *self._GetEndpoint())
  114. def _DisconnectFromTarget(self):
  115. self._ffx_target = None
  116. if self._target_context:
  117. self._target_context.__exit__(None, None, None)
  118. self._target_context = None
  119. super(EmuTarget, self)._DisconnectFromTarget()
  120. def _GetSshConfigPath(self):
  121. return boot_data.GetSSHConfigPath()
  122. def LogSystemStatistics(self, log_file_name):
  123. self._LaunchSubprocessWithLogs(['top', '-b', '-n', '1'], log_file_name)
  124. self._LaunchSubprocessWithLogs(['ps', '-ax'], log_file_name)
  125. def LogProcessStatistics(self, log_file_name):
  126. self._LaunchSubprocessWithLogs(['cat', '/proc/stat'], log_file_name)
  127. def _LaunchSubprocessWithLogs(self, command, log_file_name):
  128. """Launch a subprocess and redirect stdout and stderr to log_file_name.
  129. Command will not be run if logging directory is not set."""
  130. if not self._log_manager.IsLoggingEnabled():
  131. return
  132. log = self._log_manager.Open(log_file_name)
  133. subprocess.call(command,
  134. stdin=open(os.devnull),
  135. stdout=log,
  136. stderr=subprocess.STDOUT)