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.core import browser_credentials
8 from telemetry.core import exceptions
9 from telemetry.core import extension_dict
10 from telemetry.core import platform
11 from telemetry.core import tab_list
12 from telemetry.core import temporary_http_server
13 from telemetry.core import wpr_modes
14 from telemetry.core import wpr_server
15 from telemetry.core.backends import browser_backend
16 from telemetry.core.platform.profiler import profiler_finder
19 class Browser(object):
20 """A running browser instance that can be controlled in a limited way.
22 To create a browser instance, use browser_finder.FindBrowser.
24 Be sure to clean up after yourself by calling Close() when you are done with
25 the browser. Or better yet:
26 browser_to_create = FindBrowser(options)
27 with browser_to_create.Create() as browser:
28 ... do all your operations on browser here
30 def __init__(self, backend, platform_backend):
31 self._browser_backend = backend
32 self._http_server = None
33 self._wpr_server = None
34 self._platform = platform.Platform(platform_backend)
35 self._platform_backend = platform_backend
36 self._tabs = tab_list.TabList(backend.tab_list_backend)
37 self._extensions = None
38 if backend.supports_extensions:
39 self._extensions = extension_dict.ExtensionDict(
40 backend.extension_dict_backend)
41 self.credentials = browser_credentials.BrowserCredentials()
42 self._platform.SetFullPerformanceModeEnabled(True)
43 self._active_profilers = []
44 self._profilers_states = {}
50 def __exit__(self, *args):
58 def browser_type(self):
59 return self._browser_backend.browser_type
62 def is_content_shell(self):
63 """Returns whether this browser is a content shell, only."""
64 return self._browser_backend.is_content_shell
67 def supports_extensions(self):
68 return self._browser_backend.supports_extensions
71 def supports_tab_control(self):
72 return self._browser_backend.supports_tab_control
80 """Returns the extension dictionary if it exists."""
81 if not self.supports_extensions:
82 raise browser_backend.ExtensionsNotSupportedException(
83 'Extensions not supported')
84 return self._extensions
87 def supports_tracing(self):
88 return self._browser_backend.supports_tracing
90 def is_profiler_active(self, profiler_name):
91 return profiler_name in [profiler.name() for
92 profiler in self._active_profilers]
94 def _GetStatsCommon(self, pid_stats_function):
95 browser_pid = self._browser_backend.pid
97 'Browser': dict(pid_stats_function(browser_pid), **{'ProcessCount': 1}),
98 'Renderer': {'ProcessCount': 0},
99 'Gpu': {'ProcessCount': 0},
100 'Other': {'ProcessCount': 0}
103 for child_pid in self._platform_backend.GetChildPids(browser_pid):
105 child_cmd_line = self._platform_backend.GetCommandLine(child_pid)
106 child_stats = pid_stats_function(child_pid)
107 except exceptions.ProcessGoneException:
108 # It is perfectly fine for a process to have gone away between calling
109 # GetChildPids() and then further examining it.
111 child_process_name = self._browser_backend.GetProcessName(child_cmd_line)
112 process_name_type_key_map = {'gpu-process': 'Gpu', 'renderer': 'Renderer'}
113 if child_process_name in process_name_type_key_map:
114 child_process_type_key = process_name_type_key_map[child_process_name]
116 # TODO: identify other process types (zygote, plugin, etc), instead of
117 # lumping them in a single category.
118 child_process_type_key = 'Other'
119 result[child_process_type_key]['ProcessCount'] += 1
120 for k, v in child_stats.iteritems():
121 if k in result[child_process_type_key]:
122 result[child_process_type_key][k] += v
124 result[child_process_type_key][k] = v
126 for v in result.itervalues():
127 if v['ProcessCount'] > 1:
129 if k.endswith('Peak'):
131 del v['ProcessCount']
132 result['ProcessCount'] = process_count
136 def memory_stats(self):
137 """Returns a dict of memory statistics for the browser:
142 'WorkingSetSizePeak': V,
143 'ProportionalSetSize': W,
150 'WorkingSetSizePeak': V,
151 'ProportionalSetSize': W,
158 'WorkingSetSizePeak': V,
159 'ProportionalSetSize': W,
162 'SystemCommitCharge': Y,
165 Any of the above keys may be missing on a per-platform basis.
167 self._platform_backend.PurgeUnpinnedMemory()
168 result = self._GetStatsCommon(self._platform_backend.GetMemoryStats)
169 result['SystemCommitCharge'] = \
170 self._platform_backend.GetSystemCommitCharge()
175 """Returns a dict of cpu statistics for the system.
189 Any of the above keys may be missing on a per-platform basis.
191 result = self._GetStatsCommon(self._platform_backend.GetCpuStats)
192 del result['ProcessCount']
194 # We want a single time value, not the sum for all processes.
195 for process_type in result:
196 # Skip any process_types that are empty
197 if not len(result[process_type]):
199 result[process_type].update(self._platform_backend.GetCpuTimestamp())
204 """Returns a dict of IO statistics for the browser:
206 'ReadOperationCount': W,
207 'WriteOperationCount': X,
208 'ReadTransferCount': Y,
209 'WriteTransferCount': Z
212 'ReadOperationCount': W,
213 'WriteOperationCount': X,
214 'ReadTransferCount': Y,
215 'WriteTransferCount': Z
218 'ReadOperationCount': W,
219 'WriteOperationCount': X,
220 'ReadTransferCount': Y,
221 'WriteTransferCount': Z
225 result = self._GetStatsCommon(self._platform_backend.GetIOStats)
226 del result['ProcessCount']
229 def StartProfiling(self, profiler_name, base_output_file):
230 """Starts profiling using |profiler_name|. Results are saved to
231 |base_output_file|.<process_name>."""
232 assert not self._active_profilers, 'Already profiling. Must stop first.'
234 profiler_class = profiler_finder.FindProfiler(profiler_name)
236 if not profiler_class.is_supported(self._browser_backend.browser_type):
237 raise Exception('The %s profiler is not '
238 'supported on this platform.' % profiler_name)
240 if not profiler_class in self._profilers_states:
241 self._profilers_states[profiler_class] = {}
243 self._active_profilers.append(
244 profiler_class(self._browser_backend, self._platform_backend,
245 base_output_file, self._profilers_states[profiler_class]))
247 def StopProfiling(self):
248 """Stops all active profilers and saves their results.
251 A list of filenames produced by the profiler.
254 for profiler in self._active_profilers:
255 output_files.extend(profiler.CollectProfile())
256 self._active_profilers = []
259 def StartTracing(self, custom_categories=None, timeout=10):
260 return self._browser_backend.StartTracing(custom_categories, timeout)
262 def StopTracing(self):
263 return self._browser_backend.StopTracing()
266 browser_options = self._browser_backend.browser_options
267 if browser_options.clear_sytem_cache_for_browser_and_profile_on_start:
268 if self._platform.CanFlushIndividualFilesFromSystemCache():
269 self._platform.FlushSystemCacheForDirectory(
270 self._browser_backend.profile_directory)
271 self._platform.FlushSystemCacheForDirectory(
272 self._browser_backend.browser_directory)
274 self._platform.FlushEntireSystemCache()
276 self._browser_backend.Start()
277 self._browser_backend.SetBrowser(self)
280 """Closes this browser."""
281 for profiler_class in self._profilers_states:
282 profiler_class.WillCloseBrowser(self._browser_backend,
283 self._platform_backend)
285 self._platform.SetFullPerformanceModeEnabled(False)
288 self._wpr_server.Close()
289 self._wpr_server = None
291 if self._http_server:
292 self._http_server.Close()
293 self._http_server = None
295 self._browser_backend.Close()
296 self.credentials = None
299 def http_server(self):
300 return self._http_server
302 def SetHTTPServerDirectories(self, paths):
303 """Returns True if the HTTP server was started, False otherwise."""
304 if isinstance(paths, basestring):
306 paths = set(os.path.realpath(p) for p in paths)
308 # If any path is in a subdirectory of another, remove the subdirectory.
310 for parent_path in paths:
311 for sub_path in paths:
312 if parent_path == sub_path:
314 if os.path.commonprefix((parent_path, sub_path)) == parent_path:
315 duplicates.add(sub_path)
318 if self._http_server:
319 if paths and self._http_server.paths == paths:
322 self._http_server.Close()
323 self._http_server = None
328 self._http_server = temporary_http_server.TemporaryHTTPServer(
329 self._browser_backend, paths)
333 def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False,
334 make_javascript_deterministic=True):
336 self._wpr_server.Close()
337 self._wpr_server = None
342 if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF:
345 use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD
346 if not use_record_mode:
347 assert os.path.isfile(archive_path)
349 self._wpr_server = wpr_server.ReplayServer(
350 self._browser_backend,
353 append_to_existing_wpr,
354 make_javascript_deterministic)
356 def GetStandardOutput(self):
357 return self._browser_backend.GetStandardOutput()
359 def GetStackTrace(self):
360 return self._browser_backend.GetStackTrace()
363 def supports_system_info(self):
364 return self._browser_backend.supports_system_info
366 def GetSystemInfo(self):
367 """Returns low-level information about the system, if available.
369 See the documentation of the SystemInfo class for more details."""
370 return self._browser_backend.GetSystemInfo()