browser.py revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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 cpu_stats(self): 172 """Returns a dict of cpu statistics for the system. 173 { 'Browser': { 174 'CpuProcessTime': S, 175 'TotalTime': T 176 }, 177 'Gpu': { 178 'CpuProcessTime': S, 179 'TotalTime': T 180 }, 181 'Renderer': { 182 'CpuProcessTime': S, 183 'TotalTime': T 184 } 185 } 186 Any of the above keys may be missing on a per-platform basis. 187 """ 188 result = self._GetStatsCommon(self._platform_backend.GetCpuStats) 189 del result['ProcessCount'] 190 191 # We want a single time value, not the sum for all processes. 192 for process_type in result: 193 # Skip any process_types that are empty 194 if not len(result[process_type]): 195 continue 196 result[process_type].update(self._platform_backend.GetCpuTimestamp()) 197 return result 198 199 @property 200 def io_stats(self): 201 """Returns a dict of IO statistics for the browser: 202 { 'Browser': { 203 'ReadOperationCount': W, 204 'WriteOperationCount': X, 205 'ReadTransferCount': Y, 206 'WriteTransferCount': Z 207 }, 208 'Gpu': { 209 'ReadOperationCount': W, 210 'WriteOperationCount': X, 211 'ReadTransferCount': Y, 212 'WriteTransferCount': Z 213 }, 214 'Renderer': { 215 'ReadOperationCount': W, 216 'WriteOperationCount': X, 217 'ReadTransferCount': Y, 218 'WriteTransferCount': Z 219 } 220 } 221 """ 222 result = self._GetStatsCommon(self._platform_backend.GetIOStats) 223 del result['ProcessCount'] 224 return result 225 226 def StartProfiling(self, profiler_name, base_output_file): 227 """Starts profiling using |profiler_name|. Results are saved to 228 |base_output_file|.<process_name>.""" 229 assert not self._active_profilers, 'Already profiling. Must stop first.' 230 231 profiler_class = profiler_finder.FindProfiler(profiler_name) 232 233 if not profiler_class.is_supported(self._browser_backend.browser_type): 234 raise Exception('The %s profiler is not ' 235 'supported on this platform.' % profiler_name) 236 237 self._active_profilers.append( 238 profiler_class(self._browser_backend, self._platform_backend, 239 base_output_file)) 240 241 def StopProfiling(self): 242 """Stops all active profilers and saves their results. 243 244 Returns: 245 A list of filenames produced by the profiler. 246 """ 247 output_files = [] 248 for profiler in self._active_profilers: 249 output_files.extend(profiler.CollectProfile()) 250 self._active_profilers = [] 251 return output_files 252 253 def StartTracing(self, custom_categories=None, timeout=10): 254 return self._browser_backend.StartTracing(custom_categories, timeout) 255 256 def StopTracing(self): 257 return self._browser_backend.StopTracing() 258 259 def Start(self): 260 browser_options = self._browser_backend.browser_options 261 if browser_options.clear_sytem_cache_for_browser_and_profile_on_start: 262 if self._platform.CanFlushIndividualFilesFromSystemCache(): 263 self._platform.FlushSystemCacheForDirectory( 264 self._browser_backend.profile_directory) 265 self._platform.FlushSystemCacheForDirectory( 266 self._browser_backend.browser_directory) 267 else: 268 self._platform.FlushEntireSystemCache() 269 270 self._browser_backend.Start() 271 self._browser_backend.SetBrowser(self) 272 273 def Close(self): 274 """Closes this browser.""" 275 self._platform.SetFullPerformanceModeEnabled(False) 276 if self._wpr_server: 277 self._wpr_server.Close() 278 self._wpr_server = None 279 280 if self._http_server: 281 self._http_server.Close() 282 self._http_server = None 283 284 self._browser_backend.Close() 285 self.credentials = None 286 287 @property 288 def http_server(self): 289 return self._http_server 290 291 def SetHTTPServerDirectories(self, paths): 292 """Returns True if the HTTP server was started, False otherwise.""" 293 if not isinstance(paths, list): 294 paths = [paths] 295 paths = [os.path.abspath(p) for p in paths] 296 297 if paths and self._http_server and self._http_server.paths == paths: 298 return False 299 300 if self._http_server: 301 self._http_server.Close() 302 self._http_server = None 303 304 if not paths: 305 return False 306 307 self._http_server = temporary_http_server.TemporaryHTTPServer( 308 self._browser_backend, paths) 309 310 return True 311 312 def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False, 313 make_javascript_deterministic=True): 314 if self._wpr_server: 315 self._wpr_server.Close() 316 self._wpr_server = None 317 318 if not archive_path: 319 return None 320 321 if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF: 322 return 323 324 use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD 325 if not use_record_mode: 326 assert os.path.isfile(archive_path) 327 328 self._wpr_server = wpr_server.ReplayServer( 329 self._browser_backend, 330 archive_path, 331 use_record_mode, 332 append_to_existing_wpr, 333 make_javascript_deterministic) 334 335 def GetStandardOutput(self): 336 return self._browser_backend.GetStandardOutput() 337 338 def GetStackTrace(self): 339 return self._browser_backend.GetStackTrace() 340 341 @property 342 def supports_system_info(self): 343 return self._browser_backend.supports_system_info 344 345 def GetSystemInfo(self): 346 """Returns low-level information about the system, if available. 347 348 See the documentation of the SystemInfo class for more details.""" 349 return self._browser_backend.GetSystemInfo() 350