Home | History | Annotate | Download | only in core
      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 
      5 import os
      6 
      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 
     18 
     19 class Browser(object):
     20   """A running browser instance that can be controlled in a limited way.
     21 
     22   To create a browser instance, use browser_finder.FindBrowser.
     23 
     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
     29   """
     30   def __init__(self, backend, platform_backend, archive_path,
     31                append_to_existing_wpr, make_javascript_deterministic,
     32                credentials_path):
     33     assert platform_backend.platform != None
     34 
     35     self._browser_backend = backend
     36     self._platform_backend = platform_backend
     37     self._wpr_server = None
     38     self._local_server_controller = local_server.LocalServerController(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 
     44     self.SetReplayArchivePath(archive_path,
     45                               append_to_existing_wpr,
     46                               make_javascript_deterministic)
     47 
     48     browser_options = self._browser_backend.browser_options
     49     self.platform.FlushDnsCache()
     50     if browser_options.clear_sytem_cache_for_browser_and_profile_on_start:
     51       if self.platform.CanFlushIndividualFilesFromSystemCache():
     52         self.platform.FlushSystemCacheForDirectory(
     53             self._browser_backend.profile_directory)
     54         self.platform.FlushSystemCacheForDirectory(
     55             self._browser_backend.browser_directory)
     56       else:
     57         self.platform.FlushEntireSystemCache()
     58 
     59     self._browser_backend.SetBrowser(self)
     60     self._browser_backend.Start()
     61     self._platform_backend.DidStartBrowser(self, self._browser_backend)
     62 
     63   def __enter__(self):
     64     return self
     65 
     66   def __exit__(self, *args):
     67     self.Close()
     68 
     69   @property
     70   def platform(self):
     71     return self._platform_backend.platform
     72 
     73   @property
     74   def browser_type(self):
     75     return self._browser_backend.browser_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 synthetic_gesture_source_type(self):
     87     return self._browser_backend.browser_options.synthetic_gesture_source_type
     88 
     89   @property
     90   def tabs(self):
     91     return self._tabs
     92 
     93   @property
     94   def foreground_tab(self):
     95     for i in xrange(len(self._tabs)):
     96       # The foreground tab is the first (only) one that isn't hidden.
     97       # This only works through luck on Android, due to crbug.com/322544
     98       # which means that tabs that have never been in the foreground return
     99       # document.hidden as false; however in current code the Android foreground
    100       # tab is always tab 0, which will be the first one that isn't hidden
    101       if self._tabs[i].EvaluateJavaScript('!document.hidden'):
    102         return self._tabs[i]
    103     raise Exception("No foreground tab found")
    104 
    105   @property
    106   @decorators.Cache
    107   def extensions(self):
    108     if not self.supports_extensions:
    109       raise browser_backend.ExtensionsNotSupportedException(
    110           'Extensions not supported')
    111     return extension_dict.ExtensionDict(self._browser_backend.extension_backend)
    112 
    113   def _GetStatsCommon(self, pid_stats_function):
    114     browser_pid = self._browser_backend.pid
    115     result = {
    116         'Browser': dict(pid_stats_function(browser_pid), **{'ProcessCount': 1}),
    117         'Renderer': {'ProcessCount': 0},
    118         'Gpu': {'ProcessCount': 0},
    119         'Other': {'ProcessCount': 0}
    120     }
    121     process_count = 1
    122     for child_pid in self._platform_backend.GetChildPids(browser_pid):
    123       try:
    124         child_cmd_line = self._platform_backend.GetCommandLine(child_pid)
    125         child_stats = pid_stats_function(child_pid)
    126       except exceptions.ProcessGoneException:
    127         # It is perfectly fine for a process to have gone away between calling
    128         # GetChildPids() and then further examining it.
    129         continue
    130       child_process_name = self._browser_backend.GetProcessName(child_cmd_line)
    131       process_name_type_key_map = {'gpu-process': 'Gpu', 'renderer': 'Renderer'}
    132       if child_process_name in process_name_type_key_map:
    133         child_process_type_key = process_name_type_key_map[child_process_name]
    134       else:
    135         # TODO: identify other process types (zygote, plugin, etc), instead of
    136         # lumping them in a single category.
    137         child_process_type_key = 'Other'
    138       result[child_process_type_key]['ProcessCount'] += 1
    139       for k, v in child_stats.iteritems():
    140         if k in result[child_process_type_key]:
    141           result[child_process_type_key][k] += v
    142         else:
    143           result[child_process_type_key][k] = v
    144       process_count += 1
    145     for v in result.itervalues():
    146       if v['ProcessCount'] > 1:
    147         for k in v.keys():
    148           if k.endswith('Peak'):
    149             del v[k]
    150       del v['ProcessCount']
    151     result['ProcessCount'] = process_count
    152     return result
    153 
    154   @property
    155   def memory_stats(self):
    156     """Returns a dict of memory statistics for the browser:
    157     { 'Browser': {
    158         'VM': R,
    159         'VMPeak': S,
    160         'WorkingSetSize': T,
    161         'WorkingSetSizePeak': U,
    162         'ProportionalSetSize': V,
    163         'PrivateDirty': W
    164       },
    165       'Gpu': {
    166         'VM': R,
    167         'VMPeak': S,
    168         'WorkingSetSize': T,
    169         'WorkingSetSizePeak': U,
    170         'ProportionalSetSize': V,
    171         'PrivateDirty': W
    172       },
    173       'Renderer': {
    174         'VM': R,
    175         'VMPeak': S,
    176         'WorkingSetSize': T,
    177         'WorkingSetSizePeak': U,
    178         'ProportionalSetSize': V,
    179         'PrivateDirty': W
    180       },
    181       'SystemCommitCharge': X,
    182       'SystemTotalPhysicalMemory': Y,
    183       'ProcessCount': Z,
    184     }
    185     Any of the above keys may be missing on a per-platform basis.
    186     """
    187     self._platform_backend.PurgeUnpinnedMemory()
    188     result = self._GetStatsCommon(self._platform_backend.GetMemoryStats)
    189     commit_charge = self._platform_backend.GetSystemCommitCharge()
    190     if commit_charge:
    191       result['SystemCommitCharge'] = commit_charge
    192     total = self._platform_backend.GetSystemTotalPhysicalMemory()
    193     if total:
    194       result['SystemTotalPhysicalMemory'] = total
    195     return result
    196 
    197   @property
    198   def cpu_stats(self):
    199     """Returns a dict of cpu statistics for the system.
    200     { 'Browser': {
    201         'CpuProcessTime': S,
    202         'TotalTime': T
    203       },
    204       'Gpu': {
    205         'CpuProcessTime': S,
    206         'TotalTime': T
    207       },
    208       'Renderer': {
    209         'CpuProcessTime': S,
    210         'TotalTime': T
    211       }
    212     }
    213     Any of the above keys may be missing on a per-platform basis.
    214     """
    215     result = self._GetStatsCommon(self._platform_backend.GetCpuStats)
    216     del result['ProcessCount']
    217 
    218     # We want a single time value, not the sum for all processes.
    219     cpu_timestamp = self._platform_backend.GetCpuTimestamp()
    220     for process_type in result:
    221       # Skip any process_types that are empty
    222       if not len(result[process_type]):
    223         continue
    224       result[process_type].update(cpu_timestamp)
    225     return result
    226 
    227   @property
    228   def io_stats(self):
    229     """Returns a dict of IO statistics for the browser:
    230     { 'Browser': {
    231         'ReadOperationCount': W,
    232         'WriteOperationCount': X,
    233         'ReadTransferCount': Y,
    234         'WriteTransferCount': Z
    235       },
    236       'Gpu': {
    237         'ReadOperationCount': W,
    238         'WriteOperationCount': X,
    239         'ReadTransferCount': Y,
    240         'WriteTransferCount': Z
    241       },
    242       'Renderer': {
    243         'ReadOperationCount': W,
    244         'WriteOperationCount': X,
    245         'ReadTransferCount': Y,
    246         'WriteTransferCount': Z
    247       }
    248     }
    249     """
    250     result = self._GetStatsCommon(self._platform_backend.GetIOStats)
    251     del result['ProcessCount']
    252     return result
    253 
    254   def Close(self):
    255     """Closes this browser."""
    256     if self._browser_backend.IsBrowserRunning():
    257       self._platform_backend.WillCloseBrowser(self, self._browser_backend)
    258 
    259     if self._wpr_server:
    260       self._wpr_server.Close()
    261       self._wpr_server = None
    262 
    263     self._local_server_controller.Close()
    264     self._browser_backend.Close()
    265     self.credentials = None
    266 
    267   @property
    268   def http_server(self):
    269     return self._local_server_controller.GetRunningServer(
    270         memory_cache_http_server.MemoryCacheHTTPServer, None)
    271 
    272   def SetHTTPServerDirectories(self, paths):
    273     """Returns True if the HTTP server was started, False otherwise."""
    274     if isinstance(paths, basestring):
    275       paths = set([paths])
    276     paths = set(os.path.realpath(p) for p in paths)
    277 
    278     # If any path is in a subdirectory of another, remove the subdirectory.
    279     duplicates = set()
    280     for parent_path in paths:
    281       for sub_path in paths:
    282         if parent_path == sub_path:
    283           continue
    284         if os.path.commonprefix((parent_path, sub_path)) == parent_path:
    285           duplicates.add(sub_path)
    286     paths -= duplicates
    287 
    288     if self.http_server:
    289       if paths and self.http_server.paths == paths:
    290         return False
    291 
    292       self.http_server.Close()
    293 
    294     if not paths:
    295       return False
    296 
    297     server = memory_cache_http_server.MemoryCacheHTTPServer(paths)
    298     self.StartLocalServer(server)
    299     return True
    300 
    301   def StartLocalServer(self, server):
    302     """Starts a LocalServer and associates it with this browser.
    303 
    304     It will be closed when the browser closes.
    305     """
    306     self._local_server_controller.StartServer(server)
    307 
    308   @property
    309   def local_servers(self):
    310     """Returns the currently running local servers."""
    311     return self._local_server_controller.local_servers
    312 
    313   def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False,
    314                            make_javascript_deterministic=True):
    315     if self._wpr_server:
    316       self._wpr_server.Close()
    317       self._wpr_server = None
    318 
    319     if not archive_path:
    320       return None
    321 
    322     if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF:
    323       return
    324 
    325     use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD
    326     if not use_record_mode:
    327       assert os.path.isfile(archive_path)
    328 
    329     self._wpr_server = wpr_server.ReplayServer(
    330         self._browser_backend,
    331         archive_path,
    332         use_record_mode,
    333         append_to_existing_wpr,
    334         make_javascript_deterministic)
    335 
    336   def GetStandardOutput(self):
    337     return self._browser_backend.GetStandardOutput()
    338 
    339   def GetStackTrace(self):
    340     return self._browser_backend.GetStackTrace()
    341 
    342   @property
    343   def supports_system_info(self):
    344     return self._browser_backend.supports_system_info
    345 
    346   def GetSystemInfo(self):
    347     """Returns low-level information about the system, if available.
    348 
    349        See the documentation of the SystemInfo class for more details."""
    350     return self._browser_backend.GetSystemInfo()
    351 
    352   # TODO: Remove after call to Start() has been removed from
    353   # related authotest files.
    354   def Start(self):
    355     pass
    356