browser.py revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
1# Copyright (c) 2012 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
5import os
6
7from telemetry.core import browser_credentials
8from telemetry.core import extension_dict
9from telemetry.core import platform
10from telemetry.core import tab_list
11from telemetry.core import temporary_http_server
12from telemetry.core import wpr_modes
13from telemetry.core import wpr_server
14from telemetry.core.chrome import browser_backend
15from telemetry.core.platform.profiler import profiler_finder
16
17class Browser(object):
18  """A running browser instance that can be controlled in a limited way.
19
20  To create a browser instance, use browser_finder.FindBrowser.
21
22  Be sure to clean up after yourself by calling Close() when you are done with
23  the browser. Or better yet:
24    browser_to_create = FindBrowser(options)
25    with browser_to_create.Create() as browser:
26      ... do all your operations on browser here
27  """
28  def __init__(self, backend, platform_backend):
29    self._browser_backend = backend
30    self._http_server = None
31    self._wpr_server = None
32    self._platform = platform.Platform(platform_backend)
33    self._platform_backend = platform_backend
34    self._tabs = tab_list.TabList(backend.tab_list_backend)
35    self._extensions = None
36    if backend.supports_extensions:
37      self._extensions = extension_dict.ExtensionDict(
38          backend.extension_dict_backend)
39    self.credentials = browser_credentials.BrowserCredentials()
40    self._platform.SetFullPerformanceModeEnabled(True)
41    self._active_profilers = []
42
43  def __enter__(self):
44    return self
45
46  def __exit__(self, *args):
47    self.Close()
48
49  @property
50  def platform(self):
51    return self._platform
52
53  @property
54  def browser_type(self):
55    return self._browser_backend.browser_type
56
57  @property
58  def is_content_shell(self):
59    """Returns whether this browser is a content shell, only."""
60    return self._browser_backend.is_content_shell
61
62  @property
63  def supports_extensions(self):
64    return self._browser_backend.supports_extensions
65
66  @property
67  def supports_tab_control(self):
68    return self._browser_backend.supports_tab_control
69
70  @property
71  def tabs(self):
72    return self._tabs
73
74  @property
75  def extensions(self):
76    """Returns the extension dictionary if it exists."""
77    if not self.supports_extensions:
78      raise browser_backend.ExtensionsNotSupportedException(
79          'Extensions not supported')
80    return self._extensions
81
82  @property
83  def supports_tracing(self):
84    return self._browser_backend.supports_tracing
85
86  def is_profiler_active(self, profiler_name):
87    return profiler_name in [profiler.name() for
88                             profiler in self._active_profilers]
89
90  def _GetStatsCommon(self, pid_stats_function):
91    browser_pid = self._browser_backend.pid
92    result = {
93        'Browser': dict(pid_stats_function(browser_pid), **{'ProcessCount': 1}),
94        'Renderer': {'ProcessCount': 0},
95        'Gpu': {'ProcessCount': 0}
96    }
97    child_process_count = 0
98    for child_pid in self._platform_backend.GetChildPids(browser_pid):
99      child_process_count += 1
100      # Process type detection is causing exceptions.
101      # http://crbug.com/240951
102      try:
103        child_cmd_line = self._platform_backend.GetCommandLine(child_pid)
104        child_process_name = self._browser_backend.GetProcessName(
105            child_cmd_line)
106      except Exception:
107        child_process_name = 'renderer'
108      process_name_type_key_map = {'gpu-process': 'Gpu', 'renderer': 'Renderer'}
109      if child_process_name in process_name_type_key_map:
110        child_process_type_key = process_name_type_key_map[child_process_name]
111      else:
112        # TODO: identify other process types (zygote, plugin, etc), instead of
113        # lumping them in with renderer processes.
114        child_process_type_key = 'Renderer'
115      child_stats = pid_stats_function(child_pid)
116      result[child_process_type_key]['ProcessCount'] += 1
117      for k, v in child_stats.iteritems():
118        if k in result[child_process_type_key]:
119          result[child_process_type_key][k] += v
120        else:
121          result[child_process_type_key][k] = v
122    for v in result.itervalues():
123      if v['ProcessCount'] > 1:
124        for k in v.keys():
125          if k.endswith('Peak'):
126            del v[k]
127      del v['ProcessCount']
128    result['ProcessCount'] = child_process_count
129    return result
130
131  @property
132  def memory_stats(self):
133    """Returns a dict of memory statistics for the browser:
134    { 'Browser': {
135        'VM': S,
136        'VMPeak': T,
137        'WorkingSetSize': U,
138        'WorkingSetSizePeak': V,
139        'ProportionalSetSize': W,
140        'PrivateDirty': X
141      },
142      'Gpu': {
143        'VM': S,
144        'VMPeak': T,
145        'WorkingSetSize': U,
146        'WorkingSetSizePeak': V,
147        'ProportionalSetSize': W,
148        'PrivateDirty': X
149      },
150      'Renderer': {
151        'VM': S,
152        'VMPeak': T,
153        'WorkingSetSize': U,
154        'WorkingSetSizePeak': V,
155        'ProportionalSetSize': W,
156        'PrivateDirty': X
157      },
158      'SystemCommitCharge': Y,
159      'ProcessCount': Z,
160    }
161    Any of the above keys may be missing on a per-platform basis.
162    """
163    result = self._GetStatsCommon(self._platform_backend.GetMemoryStats)
164    result['SystemCommitCharge'] = \
165        self._platform_backend.GetSystemCommitCharge()
166    return result
167
168  @property
169  def io_stats(self):
170    """Returns a dict of IO statistics for the browser:
171    { 'Browser': {
172        'ReadOperationCount': W,
173        'WriteOperationCount': X,
174        'ReadTransferCount': Y,
175        'WriteTransferCount': Z
176      },
177      'Gpu': {
178        'ReadOperationCount': W,
179        'WriteOperationCount': X,
180        'ReadTransferCount': Y,
181        'WriteTransferCount': Z
182      },
183      'Renderer': {
184        'ReadOperationCount': W,
185        'WriteOperationCount': X,
186        'ReadTransferCount': Y,
187        'WriteTransferCount': Z
188      }
189    }
190    """
191    result = self._GetStatsCommon(self._platform_backend.GetIOStats)
192    del result['ProcessCount']
193    return result
194
195  def StartProfiling(self, options, base_output_file):
196    """Starts profiling using |options|.profiler_tool. Results are saved to
197    |base_output_file|.<process_name>."""
198    assert not self._active_profilers
199
200    profiler_class = profiler_finder.FindProfiler(options.profiler_tool)
201
202    if not profiler_class.is_supported(options):
203      raise Exception('The %s profiler is not ' +
204                      'supported on this platform.' % options.profiler_tool)
205
206    self._active_profilers.append(
207        profiler_class(self._browser_backend, self._platform_backend,
208            base_output_file))
209
210  def StopProfiling(self):
211    """Stops all active profilers and saves their results."""
212    for profiler in self._active_profilers:
213      profiler.CollectProfile()
214    self._active_profilers = []
215
216  def StartTracing(self, custom_categories=None):
217    return self._browser_backend.StartTracing(custom_categories)
218
219  def StopTracing(self):
220    return self._browser_backend.StopTracing()
221
222  def GetTraceResultAndReset(self):
223    """Returns the result of the trace, as TraceResult object."""
224    return self._browser_backend.GetTraceResultAndReset()
225
226  def Close(self):
227    """Closes this browser."""
228    self._platform.SetFullPerformanceModeEnabled(False)
229    if self._wpr_server:
230      self._wpr_server.Close()
231      self._wpr_server = None
232
233    if self._http_server:
234      self._http_server.Close()
235      self._http_server = None
236
237    self._browser_backend.Close()
238    self.credentials = None
239
240  @property
241  def http_server(self):
242    return self._http_server
243
244  def SetHTTPServerDirectories(self, paths):
245    """Returns True if the HTTP server was started, False otherwise."""
246    if not isinstance(paths, list):
247      paths = [paths]
248    paths = [os.path.abspath(p) for p in paths]
249
250    if paths and self._http_server and self._http_server.paths == paths:
251      return False
252
253    if self._http_server:
254      self._http_server.Close()
255      self._http_server = None
256
257    if not paths:
258      return False
259
260    self._http_server = temporary_http_server.TemporaryHTTPServer(
261      self._browser_backend, paths)
262
263    return True
264
265  def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False):
266    if self._wpr_server:
267      self._wpr_server.Close()
268      self._wpr_server = None
269
270    if not archive_path:
271      return None
272
273    if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF:
274      return
275
276    use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD
277    if not use_record_mode:
278      assert os.path.isfile(archive_path)
279
280    self._wpr_server = wpr_server.ReplayServer(
281        self._browser_backend,
282        archive_path,
283        use_record_mode,
284        append_to_existing_wpr,
285        self._browser_backend.WEBPAGEREPLAY_HOST,
286        self._browser_backend.webpagereplay_local_http_port,
287        self._browser_backend.webpagereplay_local_https_port,
288        self._browser_backend.webpagereplay_remote_http_port,
289        self._browser_backend.webpagereplay_remote_https_port)
290
291  def GetStandardOutput(self):
292    return self._browser_backend.GetStandardOutput()
293