• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 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 import decorators
8from telemetry.core import browser_credentials
9from telemetry.core import exceptions
10from telemetry.core import extension_dict
11from telemetry.core import local_server
12from telemetry.core import memory_cache_http_server
13from telemetry.core import platform
14from telemetry.core import tab_list
15from telemetry.core import wpr_modes
16from telemetry.core import wpr_server
17from telemetry.core.backends import browser_backend
18from telemetry.core.platform.profiler import profiler_finder
19
20
21class Browser(object):
22  """A running browser instance that can be controlled in a limited way.
23
24  To create a browser instance, use browser_finder.FindBrowser.
25
26  Be sure to clean up after yourself by calling Close() when you are done with
27  the browser. Or better yet:
28    browser_to_create = FindBrowser(options)
29    with browser_to_create.Create() as browser:
30      ... do all your operations on browser here
31  """
32  def __init__(self, backend, platform_backend):
33    self._browser_backend = backend
34    self._http_server = None
35    self._wpr_server = None
36    self._platform_backend = platform_backend
37    self._platform = platform.Platform(platform_backend)
38    self._active_profilers = []
39    self._profilers_states = {}
40    self._local_server_controller = local_server.LocalServerController(backend)
41    self._tabs = tab_list.TabList(backend.tab_list_backend)
42    self.credentials = browser_credentials.BrowserCredentials()
43    self._platform.SetFullPerformanceModeEnabled(True)
44
45  def __enter__(self):
46    self.Start()
47    return self
48
49  def __exit__(self, *args):
50    self.Close()
51
52  @property
53  def platform(self):
54    return self._platform
55
56  @property
57  def browser_type(self):
58    return self._browser_backend.browser_type
59
60  @property
61  def is_content_shell(self):
62    """Returns whether this browser is a content shell, only."""
63    return self._browser_backend.is_content_shell
64
65  @property
66  def supports_extensions(self):
67    return self._browser_backend.supports_extensions
68
69  @property
70  def supports_tab_control(self):
71    return self._browser_backend.supports_tab_control
72
73  @property
74  def synthetic_gesture_source_type(self):
75    return self._browser_backend.browser_options.synthetic_gesture_source_type
76
77  @property
78  def tabs(self):
79    return self._tabs
80
81  @property
82  def foreground_tab(self):
83    for i in xrange(len(self._tabs)):
84      # The foreground tab is the first (only) one that isn't hidden.
85      # This only works through luck on Android, due to crbug.com/322544
86      # which means that tabs that have never been in the foreground return
87      # document.hidden as false; however in current code the Android foreground
88      # tab is always tab 0, which will be the first one that isn't hidden
89      if self._tabs[i].EvaluateJavaScript('!document.hidden'):
90        return self._tabs[i]
91    raise Exception("No foreground tab found")
92
93  @property
94  @decorators.Cache
95  def extensions(self):
96    if not self.supports_extensions:
97      raise browser_backend.ExtensionsNotSupportedException(
98          'Extensions not supported')
99    return extension_dict.ExtensionDict(self._browser_backend.extension_backend)
100
101  @property
102  def supports_tracing(self):
103    return self._browser_backend.supports_tracing
104
105  def is_profiler_active(self, profiler_name):
106    return profiler_name in [profiler.name() for
107                             profiler in self._active_profilers]
108
109  def _GetStatsCommon(self, pid_stats_function):
110    browser_pid = self._browser_backend.pid
111    result = {
112        'Browser': dict(pid_stats_function(browser_pid), **{'ProcessCount': 1}),
113        'Renderer': {'ProcessCount': 0},
114        'Gpu': {'ProcessCount': 0},
115        'Other': {'ProcessCount': 0}
116    }
117    process_count = 1
118    for child_pid in self._platform_backend.GetChildPids(browser_pid):
119      try:
120        child_cmd_line = self._platform_backend.GetCommandLine(child_pid)
121        child_stats = pid_stats_function(child_pid)
122      except exceptions.ProcessGoneException:
123        # It is perfectly fine for a process to have gone away between calling
124        # GetChildPids() and then further examining it.
125        continue
126      child_process_name = self._browser_backend.GetProcessName(child_cmd_line)
127      process_name_type_key_map = {'gpu-process': 'Gpu', 'renderer': 'Renderer'}
128      if child_process_name in process_name_type_key_map:
129        child_process_type_key = process_name_type_key_map[child_process_name]
130      else:
131        # TODO: identify other process types (zygote, plugin, etc), instead of
132        # lumping them in a single category.
133        child_process_type_key = 'Other'
134      result[child_process_type_key]['ProcessCount'] += 1
135      for k, v in child_stats.iteritems():
136        if k in result[child_process_type_key]:
137          result[child_process_type_key][k] += v
138        else:
139          result[child_process_type_key][k] = v
140      process_count += 1
141    for v in result.itervalues():
142      if v['ProcessCount'] > 1:
143        for k in v.keys():
144          if k.endswith('Peak'):
145            del v[k]
146      del v['ProcessCount']
147    result['ProcessCount'] = process_count
148    return result
149
150  @property
151  def memory_stats(self):
152    """Returns a dict of memory statistics for the browser:
153    { 'Browser': {
154        'VM': R,
155        'VMPeak': S,
156        'WorkingSetSize': T,
157        'WorkingSetSizePeak': U,
158        'ProportionalSetSize': V,
159        'PrivateDirty': W
160      },
161      'Gpu': {
162        'VM': R,
163        'VMPeak': S,
164        'WorkingSetSize': T,
165        'WorkingSetSizePeak': U,
166        'ProportionalSetSize': V,
167        'PrivateDirty': W
168      },
169      'Renderer': {
170        'VM': R,
171        'VMPeak': S,
172        'WorkingSetSize': T,
173        'WorkingSetSizePeak': U,
174        'ProportionalSetSize': V,
175        'PrivateDirty': W
176      },
177      'SystemCommitCharge': X,
178      'SystemTotalPhysicalMemory': Y,
179      'ProcessCount': Z,
180    }
181    Any of the above keys may be missing on a per-platform basis.
182    """
183    self._platform_backend.PurgeUnpinnedMemory()
184    result = self._GetStatsCommon(self._platform_backend.GetMemoryStats)
185    result['SystemCommitCharge'] = \
186        self._platform_backend.GetSystemCommitCharge()
187    result['SystemTotalPhysicalMemory'] = \
188        self._platform_backend.GetSystemTotalPhysicalMemory()
189    return result
190
191  @property
192  def cpu_stats(self):
193    """Returns a dict of cpu statistics for the system.
194    { 'Browser': {
195        'CpuProcessTime': S,
196        'TotalTime': T
197      },
198      'Gpu': {
199        'CpuProcessTime': S,
200        'TotalTime': T
201      },
202      'Renderer': {
203        'CpuProcessTime': S,
204        'TotalTime': T
205      }
206    }
207    Any of the above keys may be missing on a per-platform basis.
208    """
209    result = self._GetStatsCommon(self._platform_backend.GetCpuStats)
210    del result['ProcessCount']
211
212    # We want a single time value, not the sum for all processes.
213    cpu_timestamp = self._platform_backend.GetCpuTimestamp()
214    for process_type in result:
215      # Skip any process_types that are empty
216      if not len(result[process_type]):
217        continue
218      result[process_type].update(cpu_timestamp)
219    return result
220
221  @property
222  def io_stats(self):
223    """Returns a dict of IO statistics for the browser:
224    { 'Browser': {
225        'ReadOperationCount': W,
226        'WriteOperationCount': X,
227        'ReadTransferCount': Y,
228        'WriteTransferCount': Z
229      },
230      'Gpu': {
231        'ReadOperationCount': W,
232        'WriteOperationCount': X,
233        'ReadTransferCount': Y,
234        'WriteTransferCount': Z
235      },
236      'Renderer': {
237        'ReadOperationCount': W,
238        'WriteOperationCount': X,
239        'ReadTransferCount': Y,
240        'WriteTransferCount': Z
241      }
242    }
243    """
244    result = self._GetStatsCommon(self._platform_backend.GetIOStats)
245    del result['ProcessCount']
246    return result
247
248  def StartProfiling(self, profiler_name, base_output_file):
249    """Starts profiling using |profiler_name|. Results are saved to
250    |base_output_file|.<process_name>."""
251    assert not self._active_profilers, 'Already profiling. Must stop first.'
252
253    profiler_class = profiler_finder.FindProfiler(profiler_name)
254
255    if not profiler_class.is_supported(self._browser_backend.browser_type):
256      raise Exception('The %s profiler is not '
257                      'supported on this platform.' % profiler_name)
258
259    if not profiler_class in self._profilers_states:
260      self._profilers_states[profiler_class] = {}
261
262    self._active_profilers.append(
263        profiler_class(self._browser_backend, self._platform_backend,
264            base_output_file, self._profilers_states[profiler_class]))
265
266  def StopProfiling(self):
267    """Stops all active profilers and saves their results.
268
269    Returns:
270      A list of filenames produced by the profiler.
271    """
272    output_files = []
273    for profiler in self._active_profilers:
274      output_files.extend(profiler.CollectProfile())
275    self._active_profilers = []
276    return output_files
277
278  def StartTracing(self, custom_categories=None, timeout=10):
279    return self._browser_backend.StartTracing(custom_categories, timeout)
280
281  @property
282  def is_tracing_running(self):
283    return self._browser_backend.is_tracing_running
284
285  def StopTracing(self):
286    """ Stops tracing and returns the result as TimelineData object. """
287    return self._browser_backend.StopTracing()
288
289  def Start(self):
290    browser_options = self._browser_backend.browser_options
291    self.platform.FlushDnsCache()
292    if browser_options.clear_sytem_cache_for_browser_and_profile_on_start:
293      if self.platform.CanFlushIndividualFilesFromSystemCache():
294        self.platform.FlushSystemCacheForDirectory(
295            self._browser_backend.profile_directory)
296        self.platform.FlushSystemCacheForDirectory(
297            self._browser_backend.browser_directory)
298      else:
299        self.platform.FlushEntireSystemCache()
300
301    self._browser_backend.SetBrowser(self)
302    self._browser_backend.Start()
303
304  def Close(self):
305    """Closes this browser."""
306    for profiler_class in self._profilers_states:
307      profiler_class.WillCloseBrowser(self._browser_backend,
308                                      self._platform_backend)
309
310    self.platform.SetFullPerformanceModeEnabled(False)
311
312    if self._wpr_server:
313      self._wpr_server.Close()
314      self._wpr_server = None
315
316    if self._http_server:
317      self._http_server.Close()
318      self._http_server = None
319
320    self._local_server_controller.Close()
321    self._browser_backend.Close()
322    self.credentials = None
323
324  @property
325  def http_server(self):
326    return self._local_server_controller.GetRunningServer(
327      memory_cache_http_server.MemoryCacheHTTPServer, None)
328
329  def SetHTTPServerDirectories(self, paths):
330    """Returns True if the HTTP server was started, False otherwise."""
331    if isinstance(paths, basestring):
332      paths = set([paths])
333    paths = set(os.path.realpath(p) for p in paths)
334
335    # If any path is in a subdirectory of another, remove the subdirectory.
336    duplicates = set()
337    for parent_path in paths:
338      for sub_path in paths:
339        if parent_path == sub_path:
340          continue
341        if os.path.commonprefix((parent_path, sub_path)) == parent_path:
342          duplicates.add(sub_path)
343    paths -= duplicates
344
345    if self.http_server:
346      if paths and self.http_server.paths == paths:
347        return False
348
349      self.http_server.Close()
350
351    if not paths:
352      return False
353
354    server = memory_cache_http_server.MemoryCacheHTTPServer(paths)
355    self.StartLocalServer(server)
356    return True
357
358  def StartLocalServer(self, server):
359    """Starts a LocalServer and associates it with this browser.
360
361    It will be closed when the browser closes.
362    """
363    self._local_server_controller.StartServer(server)
364
365  @property
366  def local_servers(self):
367    """Returns the currently running local servers."""
368    return self._local_server_controller.local_servers
369
370  def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False,
371                           make_javascript_deterministic=True):
372    if self._wpr_server:
373      self._wpr_server.Close()
374      self._wpr_server = None
375
376    if not archive_path:
377      return None
378
379    if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF:
380      return
381
382    use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD
383    if not use_record_mode:
384      assert os.path.isfile(archive_path)
385
386    self._wpr_server = wpr_server.ReplayServer(
387        self._browser_backend,
388        archive_path,
389        use_record_mode,
390        append_to_existing_wpr,
391        make_javascript_deterministic)
392
393  def GetStandardOutput(self):
394    return self._browser_backend.GetStandardOutput()
395
396  def GetStackTrace(self):
397    return self._browser_backend.GetStackTrace()
398
399  @property
400  def supports_system_info(self):
401    return self._browser_backend.supports_system_info
402
403  def GetSystemInfo(self):
404    """Returns low-level information about the system, if available.
405
406       See the documentation of the SystemInfo class for more details."""
407    return self._browser_backend.GetSystemInfo()
408