fvdl_target.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. # Copyright 2021 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 and interacting with Fuchsia on FVDL."""
  5. import boot_data
  6. import common
  7. import emu_target
  8. import logging
  9. import os
  10. import re
  11. import subprocess
  12. import tempfile
  13. _SSH_KEY_DIR = os.path.expanduser('~/.ssh')
  14. _DEFAULT_SSH_PORT = 22
  15. _DEVICE_PROTO_TEMPLATE = """
  16. device_spec: {{
  17. horizontal_resolution: 1024
  18. vertical_resolution: 600
  19. vm_heap: 192
  20. ram: {ramsize}
  21. cache: 32
  22. screen_density: 240
  23. }}
  24. """
  25. def GetTargetType():
  26. return FvdlTarget
  27. class EmulatorNetworkNotFoundError(Exception):
  28. """Raised when emulator's address cannot be found"""
  29. pass
  30. class FvdlTarget(emu_target.EmuTarget):
  31. EMULATOR_NAME = 'aemu'
  32. _FVDL_PATH = os.path.join(common.SDK_ROOT, 'tools', 'x64', 'fvdl')
  33. def __init__(self, out_dir, target_cpu, require_kvm, enable_graphics,
  34. hardware_gpu, with_network, cpu_cores, ram_size_mb, logs_dir,
  35. custom_image):
  36. super(FvdlTarget, self).__init__(out_dir, target_cpu, logs_dir)
  37. self._require_kvm = require_kvm
  38. self._enable_graphics = enable_graphics
  39. self._hardware_gpu = hardware_gpu
  40. self._with_network = with_network
  41. self._cpu_cores = cpu_cores
  42. self._ram_size_mb = ram_size_mb
  43. self._custom_image = custom_image
  44. self._host = None
  45. self._pid = None
  46. if custom_image:
  47. components = custom_image.split('.')
  48. if len(components) != 2:
  49. raise ValueError("Invalid custom_image name:", custom_image)
  50. self._image_type, self._image_arch = components
  51. else:
  52. self._image_arch = self._GetTargetSdkArch()
  53. self._image_type = boot_data.TARGET_TYPE_QEMU
  54. # Use a temp file for vdl output.
  55. self._vdl_output_file = tempfile.NamedTemporaryFile()
  56. # Use a temp file for the device proto and write the ram size.
  57. self._device_proto_file = tempfile.NamedTemporaryFile()
  58. with open(self._device_proto_file.name, 'w') as file:
  59. file.write(_DEVICE_PROTO_TEMPLATE.format(ramsize=self._ram_size_mb))
  60. @staticmethod
  61. def CreateFromArgs(args):
  62. return FvdlTarget(args.out_dir, args.target_cpu, args.require_kvm,
  63. args.enable_graphics, args.hardware_gpu,
  64. args.with_network, args.cpu_cores, args.ram_size_mb,
  65. args.logs_dir, args.custom_image)
  66. @staticmethod
  67. def RegisterArgs(arg_parser):
  68. fvdl_args = arg_parser.add_argument_group('fvdl', 'FVDL arguments')
  69. fvdl_args.add_argument('--with-network',
  70. action='store_true',
  71. default=False,
  72. help='Run emulator with emulated nic via tun/tap.')
  73. fvdl_args.add_argument('--custom-image',
  74. help='Specify an image used for booting up the '\
  75. 'emulator.')
  76. fvdl_args.add_argument('--enable-graphics',
  77. action='store_true',
  78. default=False,
  79. help='Start emulator with graphics instead of '\
  80. 'headless.')
  81. fvdl_args.add_argument('--hardware-gpu',
  82. action='store_true',
  83. default=False,
  84. help='Use local GPU hardware instead Swiftshader.')
  85. def _BuildCommand(self):
  86. boot_data.ProvisionSSH()
  87. self._host_ssh_port = common.GetAvailableTcpPort()
  88. kernel_image = common.EnsurePathExists(
  89. boot_data.GetTargetFile('qemu-kernel.kernel', self._image_arch,
  90. self._image_type))
  91. zbi_image = common.EnsurePathExists(
  92. boot_data.GetTargetFile('zircon-a.zbi', self._image_arch,
  93. self._image_type))
  94. fvm_image = common.EnsurePathExists(
  95. boot_data.GetTargetFile('storage-full.blk', self._image_arch,
  96. self._image_type))
  97. emu_command = [
  98. self._FVDL_PATH,
  99. '--sdk',
  100. 'start',
  101. '--nointeractive',
  102. # Host port mapping for user-networking mode.
  103. '--port-map',
  104. 'hostfwd=tcp::{}-:22'.format(self._host_ssh_port),
  105. # no-interactive requires a --vdl-output flag to shutdown the emulator.
  106. '--vdl-output',
  107. self._vdl_output_file.name,
  108. '-c',
  109. ' '.join(boot_data.GetKernelArgs()),
  110. # Use existing images instead of downloading new ones.
  111. '--kernel-image',
  112. kernel_image,
  113. '--zbi-image',
  114. zbi_image,
  115. '--fvm-image',
  116. fvm_image,
  117. '--image-architecture',
  118. self._target_cpu,
  119. # Use this flag and temp file to define ram size.
  120. '--device-proto',
  121. self._device_proto_file.name,
  122. '--cpu-count',
  123. str(self._cpu_cores)
  124. ]
  125. self._ConfigureEmulatorLog(emu_command)
  126. if not self._require_kvm:
  127. emu_command.append('--noacceleration')
  128. if not self._enable_graphics:
  129. emu_command.append('--headless')
  130. if self._hardware_gpu:
  131. emu_command.append('--host-gpu')
  132. if self._with_network:
  133. emu_command.append('-N')
  134. return emu_command
  135. def _ConfigureEmulatorLog(self, emu_command):
  136. if self._log_manager.IsLoggingEnabled():
  137. emu_command.extend([
  138. '--emulator-log',
  139. os.path.join(self._log_manager.GetLogDirectory(), 'emulator_log')
  140. ])
  141. env_flags = [
  142. 'ANDROID_EMUGL_LOG_PRINT=1',
  143. 'ANDROID_EMUGL_VERBOSE=1',
  144. 'VK_LOADER_DEBUG=info,error',
  145. ]
  146. if self._hardware_gpu:
  147. vulkan_icd_file = os.path.join(
  148. common.GetHostToolPathFromPlatform('aemu_internal'), 'lib64',
  149. 'vulkan', 'vk_swiftshader_icd.json')
  150. env_flags.append('VK_ICD_FILENAMES=%s' % vulkan_icd_file)
  151. for flag in env_flags:
  152. emu_command.extend(['--envs', flag])
  153. def _HasNetworking(self):
  154. return self._with_network
  155. def _ConnectToTarget(self):
  156. # Wait for the emulator to finish starting up.
  157. logging.info('Waiting for fvdl to start...')
  158. self._emu_process.communicate()
  159. super(FvdlTarget, self)._ConnectToTarget()
  160. def _IsEmuStillRunning(self):
  161. if not self._pid:
  162. try:
  163. with open(self._vdl_output_file.name) as vdl_file:
  164. for line in vdl_file:
  165. if 'pid' in line:
  166. match = re.match(r'.*pid:\s*(\d*).*', line)
  167. if match:
  168. self._pid = match.group(1)
  169. except IOError:
  170. logging.error('vdl_output file no longer found. '
  171. 'Cannot get emulator pid.')
  172. return False
  173. try:
  174. if subprocess.check_output(['ps', '-p', self._pid, 'o', 'comm=']):
  175. return True
  176. except subprocess.CalledProcessError:
  177. # The process must be gone.
  178. pass
  179. logging.error('Emulator pid no longer found. Emulator must be down.')
  180. return False
  181. def _GetEndpoint(self):
  182. if self._with_network:
  183. return self._GetNetworkAddress()
  184. return (self.LOCAL_ADDRESS, self._host_ssh_port)
  185. def _GetNetworkAddress(self):
  186. if self._host:
  187. return (self._host, _DEFAULT_SSH_PORT)
  188. try:
  189. with open(self._vdl_output_file.name) as vdl_file:
  190. for line in vdl_file:
  191. if 'network_address' in line:
  192. address = re.match(r'.*network_address:\s*"\[(.*)\]".*', line)
  193. if address:
  194. self._host = address.group(1)
  195. return (self._host, _DEFAULT_SSH_PORT)
  196. logging.error('Network address not found.')
  197. raise EmulatorNetworkNotFoundError()
  198. except IOError as e:
  199. logging.error('vdl_output file not found. Cannot get network address.')
  200. raise
  201. def _Shutdown(self):
  202. if not self._emu_process:
  203. logging.error('%s did not start' % (self.EMULATOR_NAME))
  204. return
  205. femu_command = [
  206. self._FVDL_PATH, '--sdk', 'kill', '--launched-proto',
  207. self._vdl_output_file.name
  208. ]
  209. femu_process = subprocess.Popen(femu_command)
  210. returncode = femu_process.wait()
  211. if returncode == 0:
  212. logging.info('FVDL shutdown successfully')
  213. else:
  214. logging.info('FVDL kill returned error status {}'.format(returncode))
  215. self.LogProcessStatistics('proc_stat_end_log')
  216. self.LogSystemStatistics('system_statistics_end_log')
  217. self._vdl_output_file.close()
  218. self._device_proto_file.close()
  219. def _GetSshConfigPath(self):
  220. return boot_data.GetSSHConfigPath()