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. 4 5import logging 6import sys 7 8from catapult_base import cloud_storage # pylint: disable=import-error 9 10from telemetry.core import exceptions 11from telemetry.core import profiling_controller 12from telemetry import decorators 13from telemetry.internal import app 14from telemetry.internal.backends import browser_backend 15from telemetry.internal.browser import browser_credentials 16from telemetry.internal.browser import extension_dict 17from telemetry.internal.browser import tab_list 18from telemetry.internal.browser import web_contents 19from telemetry.internal.util import exception_formatter 20 21 22class Browser(app.App): 23 """A running browser instance that can be controlled in a limited way. 24 25 To create a browser instance, use browser_finder.FindBrowser. 26 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(options) as browser: 31 ... do all your operations on browser here 32 """ 33 def __init__(self, backend, platform_backend, credentials_path): 34 super(Browser, self).__init__(app_backend=backend, 35 platform_backend=platform_backend) 36 try: 37 self._browser_backend = backend 38 self._platform_backend = platform_backend 39 self._tabs = tab_list.TabList(backend.tab_list_backend) 40 self.credentials = browser_credentials.BrowserCredentials() 41 self.credentials.credentials_path = credentials_path 42 self._platform_backend.DidCreateBrowser(self, self._browser_backend) 43 browser_options = self._browser_backend.browser_options 44 self.platform.FlushDnsCache() 45 if browser_options.clear_sytem_cache_for_browser_and_profile_on_start: 46 if self.platform.CanFlushIndividualFilesFromSystemCache(): 47 self.platform.FlushSystemCacheForDirectory( 48 self._browser_backend.profile_directory) 49 self.platform.FlushSystemCacheForDirectory( 50 self._browser_backend.browser_directory) 51 else: 52 self.platform.FlushEntireSystemCache() 53 54 self._browser_backend.SetBrowser(self) 55 self._browser_backend.Start() 56 self._platform_backend.DidStartBrowser(self, self._browser_backend) 57 self._profiling_controller = profiling_controller.ProfilingController( 58 self._browser_backend.profiling_controller_backend) 59 except Exception: 60 exc_info = sys.exc_info() 61 logging.exception('Failure while starting browser backend.') 62 try: 63 self._platform_backend.WillCloseBrowser(self, self._browser_backend) 64 except Exception: 65 exception_formatter.PrintFormattedException( 66 msg='Exception raised while closing platform backend') 67 raise exc_info[0], exc_info[1], exc_info[2] 68 69 @property 70 def profiling_controller(self): 71 return self._profiling_controller 72 73 @property 74 def browser_type(self): 75 return self.app_type 76 77 @property 78 def supports_extensions(self): 79 return self._browser_backend.supports_extensions 80 81 @property 82 def supports_tab_control(self): 83 return self._browser_backend.supports_tab_control 84 85 @property 86 def tabs(self): 87 return self._tabs 88 89 @property 90 def foreground_tab(self): 91 for i in xrange(len(self._tabs)): 92 # The foreground tab is the first (only) one that isn't hidden. 93 # This only works through luck on Android, due to crbug.com/322544 94 # which means that tabs that have never been in the foreground return 95 # document.hidden as false; however in current code the Android foreground 96 # tab is always tab 0, which will be the first one that isn't hidden 97 if self._tabs[i].EvaluateJavaScript('!document.hidden'): 98 return self._tabs[i] 99 raise Exception("No foreground tab found") 100 101 @property 102 @decorators.Cache 103 def extensions(self): 104 if not self.supports_extensions: 105 raise browser_backend.ExtensionsNotSupportedException( 106 'Extensions not supported') 107 return extension_dict.ExtensionDict(self._browser_backend.extension_backend) 108 109 def _GetStatsCommon(self, pid_stats_function): 110 browser_pid = self._browser_backend.pid 111 result = { 112 'Browser': dict(pid_stats_function(browser_pid), **{'ProcessCount': 1}), 113 'Renderer': {'ProcessCount': 0}, 114 'Gpu': {'ProcessCount': 0}, 115 'Other': {'ProcessCount': 0} 116 } 117 process_count = 1 118 for child_pid in self._platform_backend.GetChildPids(browser_pid): 119 try: 120 child_cmd_line = self._platform_backend.GetCommandLine(child_pid) 121 child_stats = pid_stats_function(child_pid) 122 except exceptions.ProcessGoneException: 123 # It is perfectly fine for a process to have gone away between calling 124 # GetChildPids() and then further examining it. 125 continue 126 child_process_name = self._browser_backend.GetProcessName(child_cmd_line) 127 process_name_type_key_map = {'gpu-process': 'Gpu', 'renderer': 'Renderer'} 128 if child_process_name in process_name_type_key_map: 129 child_process_type_key = process_name_type_key_map[child_process_name] 130 else: 131 # TODO: identify other process types (zygote, plugin, etc), instead of 132 # lumping them in a single category. 133 child_process_type_key = 'Other' 134 result[child_process_type_key]['ProcessCount'] += 1 135 for k, v in child_stats.iteritems(): 136 if k in result[child_process_type_key]: 137 result[child_process_type_key][k] += v 138 else: 139 result[child_process_type_key][k] = v 140 process_count += 1 141 for v in result.itervalues(): 142 if v['ProcessCount'] > 1: 143 for k in v.keys(): 144 if k.endswith('Peak'): 145 del v[k] 146 del v['ProcessCount'] 147 result['ProcessCount'] = process_count 148 return result 149 150 @property 151 def memory_stats(self): 152 """Returns a dict of memory statistics for the browser: 153 { 'Browser': { 154 'VM': R, 155 'VMPeak': S, 156 'WorkingSetSize': T, 157 'WorkingSetSizePeak': U, 158 'ProportionalSetSize': V, 159 'PrivateDirty': W 160 }, 161 'Gpu': { 162 'VM': R, 163 'VMPeak': S, 164 'WorkingSetSize': T, 165 'WorkingSetSizePeak': U, 166 'ProportionalSetSize': V, 167 'PrivateDirty': W 168 }, 169 'Renderer': { 170 'VM': R, 171 'VMPeak': S, 172 'WorkingSetSize': T, 173 'WorkingSetSizePeak': U, 174 'ProportionalSetSize': V, 175 'PrivateDirty': W 176 }, 177 'SystemCommitCharge': X, 178 'SystemTotalPhysicalMemory': Y, 179 'ProcessCount': Z, 180 } 181 Any of the above keys may be missing on a per-platform basis. 182 """ 183 self._platform_backend.PurgeUnpinnedMemory() 184 result = self._GetStatsCommon(self._platform_backend.GetMemoryStats) 185 commit_charge = self._platform_backend.GetSystemCommitCharge() 186 if commit_charge: 187 result['SystemCommitCharge'] = commit_charge 188 total = self._platform_backend.GetSystemTotalPhysicalMemory() 189 if total: 190 result['SystemTotalPhysicalMemory'] = total 191 return result 192 193 @property 194 def cpu_stats(self): 195 """Returns a dict of cpu statistics for the system. 196 { 'Browser': { 197 'CpuProcessTime': S, 198 'TotalTime': T 199 }, 200 'Gpu': { 201 'CpuProcessTime': S, 202 'TotalTime': T 203 }, 204 'Renderer': { 205 'CpuProcessTime': S, 206 'TotalTime': T 207 } 208 } 209 Any of the above keys may be missing on a per-platform basis. 210 """ 211 result = self._GetStatsCommon(self._platform_backend.GetCpuStats) 212 del result['ProcessCount'] 213 214 # We want a single time value, not the sum for all processes. 215 cpu_timestamp = self._platform_backend.GetCpuTimestamp() 216 for process_type in result: 217 # Skip any process_types that are empty 218 if not len(result[process_type]): 219 continue 220 result[process_type].update(cpu_timestamp) 221 return result 222 223 def Close(self): 224 """Closes this browser.""" 225 try: 226 if self._browser_backend.IsBrowserRunning(): 227 self._platform_backend.WillCloseBrowser(self, self._browser_backend) 228 229 self._browser_backend.profiling_controller_backend.WillCloseBrowser() 230 if self._browser_backend.supports_uploading_logs: 231 try: 232 self._browser_backend.UploadLogsToCloudStorage() 233 except cloud_storage.CloudStorageError as e: 234 logging.error('Cannot upload browser log: %s' % str(e)) 235 finally: 236 self._browser_backend.Close() 237 self.credentials = None 238 239 240 def GetStandardOutput(self): 241 return self._browser_backend.GetStandardOutput() 242 243 def GetStackTrace(self): 244 return self._browser_backend.GetStackTrace() 245 246 @property 247 def supports_system_info(self): 248 return self._browser_backend.supports_system_info 249 250 def GetSystemInfo(self): 251 """Returns low-level information about the system, if available. 252 253 See the documentation of the SystemInfo class for more details.""" 254 return self._browser_backend.GetSystemInfo() 255 256 @property 257 def supports_memory_dumping(self): 258 return self._browser_backend.supports_memory_dumping 259 260 def DumpMemory(self, timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT): 261 return self._browser_backend.DumpMemory(timeout) 262 263 @property 264 def supports_overriding_memory_pressure_notifications(self): 265 return ( 266 self._browser_backend.supports_overriding_memory_pressure_notifications) 267 268 def SetMemoryPressureNotificationsSuppressed( 269 self, suppressed, timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT): 270 self._browser_backend.SetMemoryPressureNotificationsSuppressed( 271 suppressed, timeout) 272 273 def SimulateMemoryPressureNotification( 274 self, pressure_level, timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT): 275 self._browser_backend.SimulateMemoryPressureNotification( 276 pressure_level, timeout) 277 278 @property 279 def supports_cpu_metrics(self): 280 return self._browser_backend.supports_cpu_metrics 281 282 @property 283 def supports_memory_metrics(self): 284 return self._browser_backend.supports_memory_metrics 285 286 @property 287 def supports_power_metrics(self): 288 return self._browser_backend.supports_power_metrics 289