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 io_stats(self): 172 """Returns a dict of IO statistics for the browser: 173 { 'Browser': { 174 'ReadOperationCount': W, 175 'WriteOperationCount': X, 176 'ReadTransferCount': Y, 177 'WriteTransferCount': Z 178 }, 179 'Gpu': { 180 'ReadOperationCount': W, 181 'WriteOperationCount': X, 182 'ReadTransferCount': Y, 183 'WriteTransferCount': Z 184 }, 185 'Renderer': { 186 'ReadOperationCount': W, 187 'WriteOperationCount': X, 188 'ReadTransferCount': Y, 189 'WriteTransferCount': Z 190 } 191 } 192 """ 193 result = self._GetStatsCommon(self._platform_backend.GetIOStats) 194 del result['ProcessCount'] 195 return result 196 197 def StartProfiling(self, profiler_name, base_output_file): 198 """Starts profiling using |profiler_name|. Results are saved to 199 |base_output_file|.<process_name>.""" 200 assert not self._active_profilers, 'Already profiling. Must stop first.' 201 202 profiler_class = profiler_finder.FindProfiler(profiler_name) 203 204 if not profiler_class.is_supported(self._browser_backend.options): 205 raise Exception('The %s profiler is not ' 206 'supported on this platform.' % profiler_name) 207 208 self._active_profilers.append( 209 profiler_class(self._browser_backend, self._platform_backend, 210 base_output_file)) 211 212 def StopProfiling(self): 213 """Stops all active profilers and saves their results. 214 215 Returns: 216 A list of filenames produced by the profiler. 217 """ 218 output_files = [] 219 for profiler in self._active_profilers: 220 output_files.extend(profiler.CollectProfile()) 221 self._active_profilers = [] 222 return output_files 223 224 def StartTracing(self, custom_categories=None, timeout=10): 225 return self._browser_backend.StartTracing(custom_categories, timeout) 226 227 def StopTracing(self): 228 return self._browser_backend.StopTracing() 229 230 def GetTraceResultAndReset(self): 231 """Returns the result of the trace, as TraceResult object.""" 232 return self._browser_backend.GetTraceResultAndReset() 233 234 def Start(self): 235 options = self._browser_backend.options 236 if options.clear_sytem_cache_for_browser_and_profile_on_start: 237 if self._platform.CanFlushIndividualFilesFromSystemCache(): 238 self._platform.FlushSystemCacheForDirectory( 239 self._browser_backend.profile_directory) 240 self._platform.FlushSystemCacheForDirectory( 241 self._browser_backend.browser_directory) 242 else: 243 self._platform.FlushEntireSystemCache() 244 245 self._browser_backend.Start() 246 self._browser_backend.SetBrowser(self) 247 248 def Close(self): 249 """Closes this browser.""" 250 self._platform.SetFullPerformanceModeEnabled(False) 251 if self._wpr_server: 252 self._wpr_server.Close() 253 self._wpr_server = None 254 255 if self._http_server: 256 self._http_server.Close() 257 self._http_server = None 258 259 self._browser_backend.Close() 260 self.credentials = None 261 262 @property 263 def http_server(self): 264 return self._http_server 265 266 def SetHTTPServerDirectories(self, paths): 267 """Returns True if the HTTP server was started, False otherwise.""" 268 if not isinstance(paths, list): 269 paths = [paths] 270 paths = [os.path.abspath(p) for p in paths] 271 272 if paths and self._http_server and self._http_server.paths == paths: 273 return False 274 275 if self._http_server: 276 self._http_server.Close() 277 self._http_server = None 278 279 if not paths: 280 return False 281 282 self._http_server = temporary_http_server.TemporaryHTTPServer( 283 self._browser_backend, paths) 284 285 return True 286 287 def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False, 288 make_javascript_deterministic=True): 289 if self._wpr_server: 290 self._wpr_server.Close() 291 self._wpr_server = None 292 293 if not archive_path: 294 return None 295 296 if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF: 297 return 298 299 use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD 300 if not use_record_mode: 301 assert os.path.isfile(archive_path) 302 303 self._wpr_server = wpr_server.ReplayServer( 304 self._browser_backend, 305 archive_path, 306 use_record_mode, 307 append_to_existing_wpr, 308 make_javascript_deterministic, 309 self._browser_backend.WEBPAGEREPLAY_HOST, 310 self._browser_backend.webpagereplay_local_http_port, 311 self._browser_backend.webpagereplay_local_https_port, 312 self._browser_backend.webpagereplay_remote_http_port, 313 self._browser_backend.webpagereplay_remote_https_port) 314 315 def GetStandardOutput(self): 316 return self._browser_backend.GetStandardOutput() 317 318 def GetStackTrace(self): 319 return self._browser_backend.GetStackTrace() 320