summaryrefslogtreecommitdiff
path: root/chromium/build/fuchsia/emu_target.py
blob: e1c2ace0e35b752a9c2ec39048535cbda934a43f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# Copyright 2019 The Chromium Authors
# 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)