browser.py revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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.backends 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        # The cmd line was unavailable, assume it'll be impossible to track
108        # any further stats about this process.
109        continue
110      process_name_type_key_map = {'gpu-process': 'Gpu', 'renderer': 'Renderer'}
111      if child_process_name in process_name_type_key_map:
112        child_process_type_key = process_name_type_key_map[child_process_name]
113      else:
114        # TODO: identify other process types (zygote, plugin, etc), instead of
115        # lumping them in with renderer processes.
116        child_process_type_key = 'Renderer'
117      child_stats = pid_stats_function(child_pid)
118      result[child_process_type_key]['ProcessCount'] += 1
119      for k, v in child_stats.iteritems():
120        if k in result[child_process_type_key]:
121          result[child_process_type_key][k] += v
122        else:
123          result[child_process_type_key][k] = v
124    for v in result.itervalues():
125      if v['ProcessCount'] > 1:
126        for k in v.keys():
127          if k.endswith('Peak'):
128            del v[k]
129      del v['ProcessCount']
130    result['ProcessCount'] = child_process_count
131    return result
132
133  @property
134  def memory_stats(self):
135    """Returns a dict of memory statistics for the browser:
136    { 'Browser': {
137        'VM': S,
138        'VMPeak': T,
139        'WorkingSetSize': U,
140        'WorkingSetSizePeak': V,
141        'ProportionalSetSize': W,
142        'PrivateDirty': X
143      },
144      'Gpu': {
145        'VM': S,
146        'VMPeak': T,
147        'WorkingSetSize': U,
148        'WorkingSetSizePeak': V,
149        'ProportionalSetSize': W,
150        'PrivateDirty': X
151      },
152      'Renderer': {
153        'VM': S,
154        'VMPeak': T,
155        'WorkingSetSize': U,
156        'WorkingSetSizePeak': V,
157        'ProportionalSetSize': W,
158        'PrivateDirty': X
159      },
160      'SystemCommitCharge': Y,
161      'ProcessCount': Z,
162    }
163    Any of the above keys may be missing on a per-platform basis.
164    """
165    result = self._GetStatsCommon(self._platform_backend.GetMemoryStats)
166    result['SystemCommitCharge'] = \
167        self._platform_backend.GetSystemCommitCharge()
168    return result
169
170  @property
171  def cpu_stats(self):
172    """Returns a dict of cpu statistics for the system.
173    { 'Browser': {
174        'CpuProcessTime': S,
175        'TotalTime': T
176      },
177      'Gpu': {
178        'CpuProcessTime': S,
179        'TotalTime': T
180      },
181      'Renderer': {
182        'CpuProcessTime': S,
183        'TotalTime': T
184      }
185    }
186    Any of the above keys may be missing on a per-platform basis.
187    """
188    result = self._GetStatsCommon(self._platform_backend.GetCpuStats)
189    del result['ProcessCount']
190
191    # We want a single time value, not the sum for all processes.
192    for process_type in result:
193      # Skip any process_types that are empty
194      if not len(result[process_type]):
195        continue
196      result[process_type].update(self._platform_backend.GetCpuTimestamp())
197    return result
198
199  @property
200  def io_stats(self):
201    """Returns a dict of IO statistics for the browser:
202    { 'Browser': {
203        'ReadOperationCount': W,
204        'WriteOperationCount': X,
205        'ReadTransferCount': Y,
206        'WriteTransferCount': Z
207      },
208      'Gpu': {
209        'ReadOperationCount': W,
210        'WriteOperationCount': X,
211        'ReadTransferCount': Y,
212        'WriteTransferCount': Z
213      },
214      'Renderer': {
215        'ReadOperationCount': W,
216        'WriteOperationCount': X,
217        'ReadTransferCount': Y,
218        'WriteTransferCount': Z
219      }
220    }
221    """
222    result = self._GetStatsCommon(self._platform_backend.GetIOStats)
223    del result['ProcessCount']
224    return result
225
226  def StartProfiling(self, profiler_name, base_output_file):
227    """Starts profiling using |profiler_name|. Results are saved to
228    |base_output_file|.<process_name>."""
229    assert not self._active_profilers, 'Already profiling. Must stop first.'
230
231    profiler_class = profiler_finder.FindProfiler(profiler_name)
232
233    if not profiler_class.is_supported(self._browser_backend.browser_type):
234      raise Exception('The %s profiler is not '
235                      'supported on this platform.' % profiler_name)
236
237    self._active_profilers.append(
238        profiler_class(self._browser_backend, self._platform_backend,
239            base_output_file))
240
241  def StopProfiling(self):
242    """Stops all active profilers and saves their results.
243
244    Returns:
245      A list of filenames produced by the profiler.
246    """
247    output_files = []
248    for profiler in self._active_profilers:
249      output_files.extend(profiler.CollectProfile())
250    self._active_profilers = []
251    return output_files
252
253  def StartTracing(self, custom_categories=None, timeout=10):
254    return self._browser_backend.StartTracing(custom_categories, timeout)
255
256  def StopTracing(self):
257    return self._browser_backend.StopTracing()
258
259  def Start(self):
260    browser_options = self._browser_backend.browser_options
261    if browser_options.clear_sytem_cache_for_browser_and_profile_on_start:
262      if self._platform.CanFlushIndividualFilesFromSystemCache():
263        self._platform.FlushSystemCacheForDirectory(
264            self._browser_backend.profile_directory)
265        self._platform.FlushSystemCacheForDirectory(
266            self._browser_backend.browser_directory)
267      else:
268        self._platform.FlushEntireSystemCache()
269
270    self._browser_backend.Start()
271    self._browser_backend.SetBrowser(self)
272
273  def Close(self):
274    """Closes this browser."""
275    self._platform.SetFullPerformanceModeEnabled(False)
276    if self._wpr_server:
277      self._wpr_server.Close()
278      self._wpr_server = None
279
280    if self._http_server:
281      self._http_server.Close()
282      self._http_server = None
283
284    self._browser_backend.Close()
285    self.credentials = None
286
287  @property
288  def http_server(self):
289    return self._http_server
290
291  def SetHTTPServerDirectories(self, paths):
292    """Returns True if the HTTP server was started, False otherwise."""
293    if not isinstance(paths, list):
294      paths = [paths]
295    paths = [os.path.abspath(p) for p in paths]
296
297    if paths and self._http_server and self._http_server.paths == paths:
298      return False
299
300    if self._http_server:
301      self._http_server.Close()
302      self._http_server = None
303
304    if not paths:
305      return False
306
307    self._http_server = temporary_http_server.TemporaryHTTPServer(
308      self._browser_backend, paths)
309
310    return True
311
312  def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False,
313                           make_javascript_deterministic=True):
314    if self._wpr_server:
315      self._wpr_server.Close()
316      self._wpr_server = None
317
318    if not archive_path:
319      return None
320
321    if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF:
322      return
323
324    use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD
325    if not use_record_mode:
326      assert os.path.isfile(archive_path)
327
328    self._wpr_server = wpr_server.ReplayServer(
329        self._browser_backend,
330        archive_path,
331        use_record_mode,
332        append_to_existing_wpr,
333        make_javascript_deterministic)
334
335  def GetStandardOutput(self):
336    return self._browser_backend.GetStandardOutput()
337
338  def GetStackTrace(self):
339    return self._browser_backend.GetStackTrace()
340
341  @property
342  def supports_system_info(self):
343    return self._browser_backend.supports_system_info
344
345  def GetSystemInfo(self):
346    """Returns low-level information about the system, if available.
347
348       See the documentation of the SystemInfo class for more details."""
349    return self._browser_backend.GetSystemInfo()
350