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.
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 tab_list
14 from telemetry.core import wpr_modes
15 from telemetry.core import wpr_server
16 from telemetry.core.backends import browser_backend
17 from telemetry.core.platform import tracing_category_filter
18 from telemetry.core.platform import tracing_options
19 from telemetry.core.platform.profiler import profiler_finder
22 class Browser(object):
23 """A running browser instance that can be controlled in a limited way.
25 To create a browser instance, use browser_finder.FindBrowser.
27 Be sure to clean up after yourself by calling Close() when you are done with
28 the browser. Or better yet:
29 browser_to_create = FindBrowser(options)
30 with browser_to_create.Create() as browser:
31 ... do all your operations on browser here
33 def __init__(self, backend, platform_backend):
34 assert platform_backend.platform != None
36 self._browser_backend = backend
37 self._platform_backend = platform_backend
38 self._wpr_server = None
39 self._active_profilers = []
40 self._profilers_states = {}
41 self._local_server_controller = local_server.LocalServerController(backend)
42 self._tabs = tab_list.TabList(backend.tab_list_backend)
43 self.credentials = browser_credentials.BrowserCredentials()
45 self._platform_backend.DidCreateBrowser(self, self._browser_backend)
51 def __exit__(self, *args):
56 return self._platform_backend.platform
59 def browser_type(self):
60 return self._browser_backend.browser_type
63 def supports_extensions(self):
64 return self._browser_backend.supports_extensions
67 def supports_tab_control(self):
68 return self._browser_backend.supports_tab_control
71 def synthetic_gesture_source_type(self):
72 return self._browser_backend.browser_options.synthetic_gesture_source_type
79 def foreground_tab(self):
80 for i in xrange(len(self._tabs)):
81 # The foreground tab is the first (only) one that isn't hidden.
82 # This only works through luck on Android, due to crbug.com/322544
83 # which means that tabs that have never been in the foreground return
84 # document.hidden as false; however in current code the Android foreground
85 # tab is always tab 0, which will be the first one that isn't hidden
86 if self._tabs[i].EvaluateJavaScript('!document.hidden'):
88 raise Exception("No foreground tab found")
93 if not self.supports_extensions:
94 raise browser_backend.ExtensionsNotSupportedException(
95 'Extensions not supported')
96 return extension_dict.ExtensionDict(self._browser_backend.extension_backend)
99 def supports_tracing(self):
100 return self.platform.tracing_controller.IsChromeTracingSupported(self)
102 def is_profiler_active(self, profiler_name):
103 return profiler_name in [profiler.name() for
104 profiler in self._active_profilers]
106 def _GetStatsCommon(self, pid_stats_function):
107 browser_pid = self._browser_backend.pid
109 'Browser': dict(pid_stats_function(browser_pid), **{'ProcessCount': 1}),
110 'Renderer': {'ProcessCount': 0},
111 'Gpu': {'ProcessCount': 0},
112 'Other': {'ProcessCount': 0}
115 for child_pid in self._platform_backend.GetChildPids(browser_pid):
117 child_cmd_line = self._platform_backend.GetCommandLine(child_pid)
118 child_stats = pid_stats_function(child_pid)
119 except exceptions.ProcessGoneException:
120 # It is perfectly fine for a process to have gone away between calling
121 # GetChildPids() and then further examining it.
123 child_process_name = self._browser_backend.GetProcessName(child_cmd_line)
124 process_name_type_key_map = {'gpu-process': 'Gpu', 'renderer': 'Renderer'}
125 if child_process_name in process_name_type_key_map:
126 child_process_type_key = process_name_type_key_map[child_process_name]
128 # TODO: identify other process types (zygote, plugin, etc), instead of
129 # lumping them in a single category.
130 child_process_type_key = 'Other'
131 result[child_process_type_key]['ProcessCount'] += 1
132 for k, v in child_stats.iteritems():
133 if k in result[child_process_type_key]:
134 result[child_process_type_key][k] += v
136 result[child_process_type_key][k] = v
138 for v in result.itervalues():
139 if v['ProcessCount'] > 1:
141 if k.endswith('Peak'):
143 del v['ProcessCount']
144 result['ProcessCount'] = process_count
148 def memory_stats(self):
149 """Returns a dict of memory statistics for the browser:
154 'WorkingSetSizePeak': U,
155 'ProportionalSetSize': V,
162 'WorkingSetSizePeak': U,
163 'ProportionalSetSize': V,
170 'WorkingSetSizePeak': U,
171 'ProportionalSetSize': V,
174 'SystemCommitCharge': X,
175 'SystemTotalPhysicalMemory': Y,
178 Any of the above keys may be missing on a per-platform basis.
180 self._platform_backend.PurgeUnpinnedMemory()
181 result = self._GetStatsCommon(self._platform_backend.GetMemoryStats)
182 commit_charge = self._platform_backend.GetSystemCommitCharge()
184 result['SystemCommitCharge'] = commit_charge
185 total = self._platform_backend.GetSystemTotalPhysicalMemory()
187 result['SystemTotalPhysicalMemory'] = total
192 """Returns a dict of cpu statistics for the system.
206 Any of the above keys may be missing on a per-platform basis.
208 result = self._GetStatsCommon(self._platform_backend.GetCpuStats)
209 del result['ProcessCount']
211 # We want a single time value, not the sum for all processes.
212 cpu_timestamp = self._platform_backend.GetCpuTimestamp()
213 for process_type in result:
214 # Skip any process_types that are empty
215 if not len(result[process_type]):
217 result[process_type].update(cpu_timestamp)
222 """Returns a dict of IO statistics for the browser:
224 'ReadOperationCount': W,
225 'WriteOperationCount': X,
226 'ReadTransferCount': Y,
227 'WriteTransferCount': Z
230 'ReadOperationCount': W,
231 'WriteOperationCount': X,
232 'ReadTransferCount': Y,
233 'WriteTransferCount': Z
236 'ReadOperationCount': W,
237 'WriteOperationCount': X,
238 'ReadTransferCount': Y,
239 'WriteTransferCount': Z
243 result = self._GetStatsCommon(self._platform_backend.GetIOStats)
244 del result['ProcessCount']
247 def StartProfiling(self, profiler_name, base_output_file):
248 """Starts profiling using |profiler_name|. Results are saved to
249 |base_output_file|.<process_name>."""
250 assert not self._active_profilers, 'Already profiling. Must stop first.'
252 profiler_class = profiler_finder.FindProfiler(profiler_name)
254 if not profiler_class.is_supported(self._browser_backend.browser_type):
255 raise Exception('The %s profiler is not '
256 'supported on this platform.' % profiler_name)
258 if not profiler_class in self._profilers_states:
259 self._profilers_states[profiler_class] = {}
261 self._active_profilers.append(
262 profiler_class(self._browser_backend, self._platform_backend,
263 base_output_file, self._profilers_states[profiler_class]))
265 def StopProfiling(self):
266 """Stops all active profilers and saves their results.
269 A list of filenames produced by the profiler.
272 for profiler in self._active_profilers:
273 output_files.extend(profiler.CollectProfile())
274 self._active_profilers = []
278 def StartTracing(self, custom_categories=None, timeout=10):
279 """Note: this function is deprecated. Prefer platform.tracing_controller."""
280 if not isinstance(custom_categories,
281 tracing_category_filter.TracingCategoryFilter):
282 category_filter = tracing_category_filter.TracingCategoryFilter(
283 filter_string=custom_categories)
285 category_filter = custom_categories
286 options = tracing_options.TracingOptions()
287 options.enable_chrome_trace = True
288 return self.platform.tracing_controller.Start(
289 options, category_filter, timeout)
292 def is_tracing_running(self):
293 """Note: this function is deprecated. Prefer platform.tracing_controller."""
294 return self.platform.tracing_controller.is_tracing_running
296 def StopTracing(self):
297 """Note: this function is deprecated. Prefer platform.tracing_controller."""
298 return self.platform.tracing_controller.Stop()
301 browser_options = self._browser_backend.browser_options
302 self.platform.FlushDnsCache()
303 if browser_options.clear_sytem_cache_for_browser_and_profile_on_start:
304 if self.platform.CanFlushIndividualFilesFromSystemCache():
305 self.platform.FlushSystemCacheForDirectory(
306 self._browser_backend.profile_directory)
307 self.platform.FlushSystemCacheForDirectory(
308 self._browser_backend.browser_directory)
310 self.platform.FlushEntireSystemCache()
312 self._browser_backend.SetBrowser(self)
313 self._browser_backend.Start()
314 self._platform_backend.DidStartBrowser(self, self._browser_backend)
317 """Closes this browser."""
318 for profiler_class in self._profilers_states:
319 profiler_class.WillCloseBrowser(self._browser_backend,
320 self._platform_backend)
322 if self._browser_backend.IsBrowserRunning():
323 self._platform_backend.WillCloseBrowser(self, self._browser_backend)
326 self._wpr_server.Close()
327 self._wpr_server = None
329 self._local_server_controller.Close()
330 self._browser_backend.Close()
331 self.credentials = None
334 def http_server(self):
335 return self._local_server_controller.GetRunningServer(
336 memory_cache_http_server.MemoryCacheHTTPServer, None)
338 def SetHTTPServerDirectories(self, paths):
339 """Returns True if the HTTP server was started, False otherwise."""
340 if isinstance(paths, basestring):
342 paths = set(os.path.realpath(p) for p in paths)
344 # If any path is in a subdirectory of another, remove the subdirectory.
346 for parent_path in paths:
347 for sub_path in paths:
348 if parent_path == sub_path:
350 if os.path.commonprefix((parent_path, sub_path)) == parent_path:
351 duplicates.add(sub_path)
355 if paths and self.http_server.paths == paths:
358 self.http_server.Close()
363 server = memory_cache_http_server.MemoryCacheHTTPServer(paths)
364 self.StartLocalServer(server)
367 def StartLocalServer(self, server):
368 """Starts a LocalServer and associates it with this browser.
370 It will be closed when the browser closes.
372 self._local_server_controller.StartServer(server)
375 def local_servers(self):
376 """Returns the currently running local servers."""
377 return self._local_server_controller.local_servers
379 def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False,
380 make_javascript_deterministic=True):
382 self._wpr_server.Close()
383 self._wpr_server = None
388 if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF:
391 use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD
392 if not use_record_mode:
393 assert os.path.isfile(archive_path)
395 self._wpr_server = wpr_server.ReplayServer(
396 self._browser_backend,
399 append_to_existing_wpr,
400 make_javascript_deterministic)
402 def GetStandardOutput(self):
403 return self._browser_backend.GetStandardOutput()
405 def GetStackTrace(self):
406 return self._browser_backend.GetStackTrace()
409 def supports_system_info(self):
410 return self._browser_backend.supports_system_info
412 def GetSystemInfo(self):
413 """Returns low-level information about the system, if available.
415 See the documentation of the SystemInfo class for more details."""
416 return self._browser_backend.GetSystemInfo()