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.
7 from telemetry import decorators
8 from telemetry.core import browser_credentials
9 from telemetry.core import exceptions
10 from telemetry.core import extension_dict
11 from telemetry.core import local_server
12 from telemetry.core import memory_cache_http_server
13 from telemetry.core import platform
14 from telemetry.core import tab_list
15 from telemetry.core import wpr_modes
16 from telemetry.core import wpr_server
17 from telemetry.core.backends import browser_backend
18 from telemetry.core.platform.profiler import profiler_finder
21 class Browser(object):
22 """A running browser instance that can be controlled in a limited way.
24 To create a browser instance, use browser_finder.FindBrowser.
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
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._active_profilers = []
38 self._profilers_states = {}
40 self._extensions = None
41 if backend.supports_extensions:
42 self._extensions = extension_dict.ExtensionDict(
43 backend.extension_dict_backend)
45 self._local_server_controller = local_server.LocalServerController(backend)
46 self._tabs = tab_list.TabList(backend.tab_list_backend)
47 self.credentials = browser_credentials.BrowserCredentials()
48 self.platform.SetFullPerformanceModeEnabled(True)
54 def __exit__(self, *args):
60 return platform.Platform(self._platform_backend)
63 def browser_type(self):
64 return self._browser_backend.browser_type
67 def is_content_shell(self):
68 """Returns whether this browser is a content shell, only."""
69 return self._browser_backend.is_content_shell
72 def supports_extensions(self):
73 return self._browser_backend.supports_extensions
76 def supports_tab_control(self):
77 return self._browser_backend.supports_tab_control
80 def synthetic_gesture_source_type(self):
81 return self._browser_backend.browser_options.synthetic_gesture_source_type
88 def foreground_tab(self):
89 for i in xrange(len(self._tabs)):
90 # The foreground tab is the first (only) one that isn't hidden.
91 # This only works through luck on Android, due to crbug.com/322544
92 # which means that tabs that have never been in the foreground return
93 # document.hidden as false; however in current code the Android foreground
94 # tab is always tab 0, which will be the first one that isn't hidden
95 if self._tabs[i].EvaluateJavaScript('!document.hidden'):
97 raise Exception("No foreground tab found")
100 def extensions(self):
101 """Returns the extension dictionary if it exists."""
102 if not self.supports_extensions:
103 raise browser_backend.ExtensionsNotSupportedException(
104 'Extensions not supported')
105 return self._extensions
108 def supports_tracing(self):
109 return self._browser_backend.supports_tracing
111 def is_profiler_active(self, profiler_name):
112 return profiler_name in [profiler.name() for
113 profiler in self._active_profilers]
115 def _GetStatsCommon(self, pid_stats_function):
116 browser_pid = self._browser_backend.pid
118 'Browser': dict(pid_stats_function(browser_pid), **{'ProcessCount': 1}),
119 'Renderer': {'ProcessCount': 0},
120 'Gpu': {'ProcessCount': 0},
121 'Other': {'ProcessCount': 0}
124 for child_pid in self._platform_backend.GetChildPids(browser_pid):
126 child_cmd_line = self._platform_backend.GetCommandLine(child_pid)
127 child_stats = pid_stats_function(child_pid)
128 except exceptions.ProcessGoneException:
129 # It is perfectly fine for a process to have gone away between calling
130 # GetChildPids() and then further examining it.
132 child_process_name = self._browser_backend.GetProcessName(child_cmd_line)
133 process_name_type_key_map = {'gpu-process': 'Gpu', 'renderer': 'Renderer'}
134 if child_process_name in process_name_type_key_map:
135 child_process_type_key = process_name_type_key_map[child_process_name]
137 # TODO: identify other process types (zygote, plugin, etc), instead of
138 # lumping them in a single category.
139 child_process_type_key = 'Other'
140 result[child_process_type_key]['ProcessCount'] += 1
141 for k, v in child_stats.iteritems():
142 if k in result[child_process_type_key]:
143 result[child_process_type_key][k] += v
145 result[child_process_type_key][k] = v
147 for v in result.itervalues():
148 if v['ProcessCount'] > 1:
150 if k.endswith('Peak'):
152 del v['ProcessCount']
153 result['ProcessCount'] = process_count
157 def memory_stats(self):
158 """Returns a dict of memory statistics for the browser:
163 'WorkingSetSizePeak': U,
164 'ProportionalSetSize': V,
171 'WorkingSetSizePeak': U,
172 'ProportionalSetSize': V,
179 'WorkingSetSizePeak': U,
180 'ProportionalSetSize': V,
183 'SystemCommitCharge': X,
184 'SystemTotalPhysicalMemory': Y,
187 Any of the above keys may be missing on a per-platform basis.
189 self._platform_backend.PurgeUnpinnedMemory()
190 result = self._GetStatsCommon(self._platform_backend.GetMemoryStats)
191 result['SystemCommitCharge'] = \
192 self._platform_backend.GetSystemCommitCharge()
193 result['SystemTotalPhysicalMemory'] = \
194 self._platform_backend.GetSystemTotalPhysicalMemory()
199 """Returns a dict of cpu statistics for the system.
213 Any of the above keys may be missing on a per-platform basis.
215 result = self._GetStatsCommon(self._platform_backend.GetCpuStats)
216 del result['ProcessCount']
218 # We want a single time value, not the sum for all processes.
219 for process_type in result:
220 # Skip any process_types that are empty
221 if not len(result[process_type]):
223 result[process_type].update(self._platform_backend.GetCpuTimestamp())
228 """Returns a dict of IO statistics for the browser:
230 'ReadOperationCount': W,
231 'WriteOperationCount': X,
232 'ReadTransferCount': Y,
233 'WriteTransferCount': Z
236 'ReadOperationCount': W,
237 'WriteOperationCount': X,
238 'ReadTransferCount': Y,
239 'WriteTransferCount': Z
242 'ReadOperationCount': W,
243 'WriteOperationCount': X,
244 'ReadTransferCount': Y,
245 'WriteTransferCount': Z
249 result = self._GetStatsCommon(self._platform_backend.GetIOStats)
250 del result['ProcessCount']
253 def StartProfiling(self, profiler_name, base_output_file):
254 """Starts profiling using |profiler_name|. Results are saved to
255 |base_output_file|.<process_name>."""
256 assert not self._active_profilers, 'Already profiling. Must stop first.'
258 profiler_class = profiler_finder.FindProfiler(profiler_name)
260 if not profiler_class.is_supported(self._browser_backend.browser_type):
261 raise Exception('The %s profiler is not '
262 'supported on this platform.' % profiler_name)
264 if not profiler_class in self._profilers_states:
265 self._profilers_states[profiler_class] = {}
267 self._active_profilers.append(
268 profiler_class(self._browser_backend, self._platform_backend,
269 base_output_file, self._profilers_states[profiler_class]))
271 def StopProfiling(self):
272 """Stops all active profilers and saves their results.
275 A list of filenames produced by the profiler.
278 for profiler in self._active_profilers:
279 output_files.extend(profiler.CollectProfile())
280 self._active_profilers = []
283 def StartTracing(self, custom_categories=None, timeout=10):
284 return self._browser_backend.StartTracing(custom_categories, timeout)
286 def StopTracing(self):
287 return self._browser_backend.StopTracing()
290 browser_options = self._browser_backend.browser_options
291 if browser_options.clear_sytem_cache_for_browser_and_profile_on_start:
292 if self.platform.CanFlushIndividualFilesFromSystemCache():
293 self.platform.FlushSystemCacheForDirectory(
294 self._browser_backend.profile_directory)
295 self.platform.FlushSystemCacheForDirectory(
296 self._browser_backend.browser_directory)
298 self.platform.FlushEntireSystemCache()
300 self._browser_backend.Start()
301 self._browser_backend.SetBrowser(self)
304 """Closes this browser."""
305 for profiler_class in self._profilers_states:
306 profiler_class.WillCloseBrowser(self._browser_backend,
307 self._platform_backend)
309 self.platform.SetFullPerformanceModeEnabled(False)
312 self._wpr_server.Close()
313 self._wpr_server = None
315 if self._http_server:
316 self._http_server.Close()
317 self._http_server = None
319 self._local_server_controller.Close()
320 self._browser_backend.Close()
321 self.credentials = None
324 def http_server(self):
325 return self._local_server_controller.GetRunningServer(
326 memory_cache_http_server.MemoryCacheHTTPServer, None)
328 def SetHTTPServerDirectories(self, paths):
329 """Returns True if the HTTP server was started, False otherwise."""
330 if isinstance(paths, basestring):
332 paths = set(os.path.realpath(p) for p in paths)
334 # If any path is in a subdirectory of another, remove the subdirectory.
336 for parent_path in paths:
337 for sub_path in paths:
338 if parent_path == sub_path:
340 if os.path.commonprefix((parent_path, sub_path)) == parent_path:
341 duplicates.add(sub_path)
345 if paths and self.http_server.paths == paths:
348 self.http_server.Close()
353 server = memory_cache_http_server.MemoryCacheHTTPServer(paths)
354 self.StartLocalServer(server)
357 def StartLocalServer(self, server):
358 """Starts a LocalServer and associates it with this browser.
360 It will be closed when the browser closes.
362 self._local_server_controller.StartServer(server)
365 def local_servers(self):
366 """Returns the currently running local servers."""
367 return self._local_server_controller.local_servers
369 def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False,
370 make_javascript_deterministic=True):
372 self._wpr_server.Close()
373 self._wpr_server = None
378 if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF:
381 use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD
382 if not use_record_mode:
383 assert os.path.isfile(archive_path)
385 self._wpr_server = wpr_server.ReplayServer(
386 self._browser_backend,
389 append_to_existing_wpr,
390 make_javascript_deterministic)
392 def GetStandardOutput(self):
393 return self._browser_backend.GetStandardOutput()
395 def GetStackTrace(self):
396 return self._browser_backend.GetStackTrace()
399 def supports_system_info(self):
400 return self._browser_backend.supports_system_info
402 def GetSystemInfo(self):
403 """Returns low-level information about the system, if available.
405 See the documentation of the SystemInfo class for more details."""
406 return self._browser_backend.GetSystemInfo()