Home | History | Annotate | Download | only in page
      1 # Copyright 2014 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 logging
      6 import os
      7 import sys
      8 
      9 from telemetry.core import exceptions
     10 from telemetry.core import util
     11 from telemetry import decorators
     12 from telemetry.internal.browser import browser_finder
     13 from telemetry.internal.browser import browser_finder_exceptions
     14 from telemetry.internal.browser import browser_info as browser_info_module
     15 from telemetry.internal.platform.profiler import profiler_finder
     16 from telemetry.internal.util import exception_formatter
     17 from telemetry.internal.util import file_handle
     18 from telemetry.page import cache_temperature
     19 from telemetry.page import traffic_setting
     20 from telemetry.page import legacy_page_test
     21 from telemetry import story
     22 from telemetry.util import screenshot
     23 from telemetry.util import wpr_modes
     24 from telemetry.web_perf import timeline_based_measurement
     25 
     26 
     27 def _PrepareFinderOptions(finder_options, test, device_type):
     28   browser_options = finder_options.browser_options
     29   # Set up user agent.
     30   browser_options.browser_user_agent_type = device_type
     31 
     32   test.CustomizeBrowserOptions(finder_options.browser_options)
     33   if finder_options.profiler:
     34     profiler_class = profiler_finder.FindProfiler(finder_options.profiler)
     35     profiler_class.CustomizeBrowserOptions(browser_options.browser_type,
     36                                            finder_options)
     37 
     38 
     39 class SharedPageState(story.SharedState):
     40   """
     41   This class contains all specific logic necessary to run a Chrome browser
     42   benchmark.
     43   """
     44 
     45   _device_type = None
     46 
     47   def __init__(self, test, finder_options, story_set):
     48     super(SharedPageState, self).__init__(test, finder_options, story_set)
     49     if isinstance(test, timeline_based_measurement.TimelineBasedMeasurement):
     50       if finder_options.profiler:
     51         assert not 'trace' in finder_options.profiler, (
     52             'This is a Timeline Based Measurement benchmark. You cannot run it '
     53             'with trace profiler enabled. If you need trace data, tracing is '
     54             'always enabled in Timeline Based Measurement benchmarks and you '
     55             'can get the trace data by adding --output-format=json.')
     56       # This is to avoid the cyclic-import caused by timeline_based_page_test.
     57       from telemetry.web_perf import timeline_based_page_test
     58       self._test = timeline_based_page_test.TimelineBasedPageTest(test)
     59     else:
     60       self._test = test
     61     _PrepareFinderOptions(finder_options, self._test, self._device_type)
     62     self._browser = None
     63     self._finder_options = finder_options
     64     self._possible_browser = self._GetPossibleBrowser(
     65         self._test, finder_options)
     66 
     67     self._first_browser = True
     68     self._did_login_for_current_page = False
     69     self._previous_page = None
     70     self._current_page = None
     71     self._current_tab = None
     72 
     73     self._test.SetOptions(self._finder_options)
     74 
     75     # TODO(crbug/404771): Move network controller options out of
     76     # browser_options and into finder_options.
     77     browser_options = self._finder_options.browser_options
     78     if self._finder_options.use_live_sites:
     79       wpr_mode = wpr_modes.WPR_OFF
     80     elif browser_options.wpr_mode == wpr_modes.WPR_RECORD:
     81       wpr_mode = wpr_modes.WPR_RECORD
     82     else:
     83       wpr_mode = wpr_modes.WPR_REPLAY
     84 
     85     use_live_traffic = wpr_mode == wpr_modes.WPR_OFF
     86 
     87     if self.platform.network_controller.is_open:
     88       self.platform.network_controller.Close()
     89     self.platform.network_controller.InitializeIfNeeded(
     90         use_live_traffic=use_live_traffic)
     91     self.platform.network_controller.Open(wpr_mode,
     92                                           browser_options.extra_wpr_args)
     93     self.platform.Initialize()
     94 
     95   @property
     96   def possible_browser(self):
     97     return self._possible_browser
     98 
     99   @property
    100   def browser(self):
    101     return self._browser
    102 
    103   def _FindBrowser(self, finder_options):
    104     possible_browser = browser_finder.FindBrowser(finder_options)
    105     if not possible_browser:
    106       raise browser_finder_exceptions.BrowserFinderException(
    107           'No browser found.\n\nAvailable browsers:\n%s\n' %
    108           '\n'.join(browser_finder.GetAllAvailableBrowserTypes(finder_options)))
    109     return possible_browser
    110 
    111   def _GetPossibleBrowser(self, test, finder_options):
    112     """Return a possible_browser with the given options for |test|. """
    113     possible_browser = self._FindBrowser(finder_options)
    114     finder_options.browser_options.browser_type = (
    115         possible_browser.browser_type)
    116 
    117     enabled, msg = decorators.IsEnabled(test, possible_browser)
    118     if not enabled and not finder_options.run_disabled_tests:
    119       logging.warning(msg)
    120       logging.warning('You are trying to run a disabled test.')
    121 
    122     if possible_browser.IsRemote():
    123       possible_browser.RunRemote()
    124       sys.exit(0)
    125     return possible_browser
    126 
    127   def DumpStateUponFailure(self, page, results):
    128     # Dump browser standard output and log.
    129     if self._browser:
    130       self._browser.DumpStateUponFailure()
    131     else:
    132       logging.warning('Cannot dump browser state: No browser.')
    133 
    134     # Capture a screenshot
    135     if self._finder_options.browser_options.take_screenshot_for_failed_page:
    136       fh = screenshot.TryCaptureScreenShot(self.platform, self._current_tab)
    137       if fh is not None:
    138         results.AddProfilingFile(page, fh)
    139     else:
    140       logging.warning('Taking screenshots upon failures disabled.')
    141 
    142   def DidRunStory(self, results):
    143     if self._finder_options.profiler:
    144       self._StopProfiling(results)
    145     # We might hang while trying to close the connection, and need to guarantee
    146     # the page will get cleaned up to avoid future tests failing in weird ways.
    147     try:
    148       if self._current_tab and self._current_tab.IsAlive():
    149         self._current_tab.CloseConnections()
    150       self._previous_page = self._current_page
    151     except Exception:
    152       if self._current_tab:
    153         self._current_tab.Close()
    154     finally:
    155       if self._current_page.credentials and self._did_login_for_current_page:
    156         self.browser.credentials.LoginNoLongerNeeded(
    157             self._current_tab, self._current_page.credentials)
    158       if self._test.StopBrowserAfterPage(self.browser, self._current_page):
    159         self._StopBrowser()
    160       self._current_page = None
    161       self._current_tab = None
    162 
    163   @property
    164   def platform(self):
    165     return self._possible_browser.platform
    166 
    167   def _StartBrowser(self, page):
    168     assert self._browser is None
    169     self._possible_browser.SetCredentialsPath(page.credentials_path)
    170 
    171     self._test.WillStartBrowser(self.platform)
    172     if page.startup_url:
    173       self._finder_options.browser_options.startup_url = page.startup_url
    174     self._browser = self._possible_browser.Create(self._finder_options)
    175     self._test.DidStartBrowser(self.browser)
    176 
    177     if self._first_browser:
    178       self._first_browser = False
    179       self.browser.credentials.WarnIfMissingCredentials(page)
    180 
    181   def WillRunStory(self, page):
    182     if not self.platform.tracing_controller.is_tracing_running:
    183       # For TimelineBasedMeasurement benchmarks, tracing has already started.
    184       # For PageTest benchmarks, tracing has not yet started. We need to make
    185       # sure no tracing state is left before starting the browser for PageTest
    186       # benchmarks.
    187       self.platform.tracing_controller.ClearStateIfNeeded()
    188 
    189     page_set = page.page_set
    190     self._current_page = page
    191     if self._browser and (self._test.RestartBrowserBeforeEachPage()
    192                           or page.startup_url):
    193       assert not self.platform.tracing_controller.is_tracing_running, (
    194           'Should not restart browser when tracing is already running. For '
    195           'TimelineBasedMeasurement (TBM) benchmarks, you should not use '
    196           'startup_url. Use benchmark.ShouldTearDownStateAfterEachStoryRun '
    197           'instead.')
    198       self._StopBrowser()
    199     started_browser = not self.browser
    200 
    201     archive_path = page_set.WprFilePathForStory(page, self.platform.GetOSName())
    202     # TODO(nednguyen, perezju): Ideally we should just let the network
    203     # controller raise an exception when the archive_path is not found.
    204     if archive_path is not None and not os.path.isfile(archive_path):
    205       logging.warning('WPR archive missing: %s', archive_path)
    206       archive_path = None
    207     self.platform.network_controller.StartReplay(
    208         archive_path, page.make_javascript_deterministic)
    209 
    210     if self.browser:
    211       # Set new credential path for browser.
    212       self.browser.credentials.credentials_path = page.credentials_path
    213     else:
    214       self._StartBrowser(page)
    215     if self.browser.supports_tab_control and self._test.close_tabs_before_run:
    216       # Create a tab if there's none.
    217       if len(self.browser.tabs) == 0:
    218         self.browser.tabs.New()
    219 
    220       # Ensure only one tab is open, unless the test is a multi-tab test.
    221       if not self._test.is_multi_tab_test:
    222         while len(self.browser.tabs) > 1:
    223           self.browser.tabs[-1].Close()
    224 
    225       # Must wait for tab to commit otherwise it can commit after the next
    226       # navigation has begun and RenderFrameHostManager::DidNavigateMainFrame()
    227       # will cancel the next navigation because it's pending. This manifests as
    228       # the first navigation in a PageSet freezing indefinitely because the
    229       # navigation was silently canceled when |self.browser.tabs[0]| was
    230       # committed. Only do this when we just started the browser, otherwise
    231       # there are cases where previous pages in a PageSet never complete
    232       # loading so we'll wait forever.
    233       if started_browser:
    234         self.browser.tabs[0].WaitForDocumentReadyStateToBeComplete()
    235 
    236     # Reset traffic shaping to speed up cache temperature setup.
    237     self.platform.network_controller.UpdateTrafficSettings(0, 0, 0)
    238     cache_temperature.EnsurePageCacheTemperature(
    239         self._current_page, self.browser, self._previous_page)
    240     if self._current_page.traffic_setting != traffic_setting.NONE:
    241       s = traffic_setting.NETWORK_CONFIGS[self._current_page.traffic_setting]
    242       self.platform.network_controller.UpdateTrafficSettings(
    243           round_trip_latency_ms=s.round_trip_latency_ms,
    244           download_bandwidth_kbps=s.download_bandwidth_kbps,
    245           upload_bandwidth_kbps=s.upload_bandwidth_kbps)
    246 
    247     # Start profiling if needed.
    248     if self._finder_options.profiler:
    249       self._StartProfiling(self._current_page)
    250 
    251   def CanRunStory(self, page):
    252     return self.CanRunOnBrowser(browser_info_module.BrowserInfo(self.browser),
    253                                 page)
    254 
    255   def CanRunOnBrowser(self, browser_info,
    256                       page):  # pylint: disable=unused-argument
    257     """Override this to return whether the browser brought up by this state
    258     instance is suitable for running the given page.
    259 
    260     Args:
    261       browser_info: an instance of telemetry.core.browser_info.BrowserInfo
    262       page: an instance of telemetry.page.Page
    263     """
    264     del browser_info, page  # unused
    265     return True
    266 
    267   def _PreparePage(self):
    268     self._current_tab = self._test.TabForPage(self._current_page, self.browser)
    269     if self._current_page.is_file:
    270       self.platform.SetHTTPServerDirectories(
    271           self._current_page.page_set.serving_dirs |
    272           set([self._current_page.serving_dir]))
    273 
    274     if self._current_page.credentials:
    275       if not self.browser.credentials.LoginNeeded(
    276           self._current_tab, self._current_page.credentials):
    277         raise legacy_page_test.Failure(
    278             'Login as ' + self._current_page.credentials + ' failed')
    279       self._did_login_for_current_page = True
    280 
    281     if self._test.clear_cache_before_each_run:
    282       self._current_tab.ClearCache(force=True)
    283 
    284   @property
    285   def current_page(self):
    286     return self._current_page
    287 
    288   @property
    289   def current_tab(self):
    290     return self._current_tab
    291 
    292   @property
    293   def page_test(self):
    294     return self._test
    295 
    296   def RunStory(self, results):
    297     try:
    298       self._PreparePage()
    299       self._current_page.Run(self)
    300       self._test.ValidateAndMeasurePage(
    301           self._current_page, self._current_tab, results)
    302     except exceptions.Error:
    303       if self._test.is_multi_tab_test:
    304         # Avoid trying to recover from an unknown multi-tab state.
    305         exception_formatter.PrintFormattedException(
    306             msg='Telemetry Error during multi tab test:')
    307         raise legacy_page_test.MultiTabTestAppCrashError
    308       raise
    309 
    310   def TearDownState(self):
    311     self._StopBrowser()
    312     self.platform.StopAllLocalServers()
    313     self.platform.network_controller.Close()
    314 
    315   def _StopBrowser(self):
    316     if self._browser:
    317       self._browser.Close()
    318       self._browser = None
    319 
    320   def _StartProfiling(self, page):
    321     output_file = os.path.join(self._finder_options.output_dir,
    322                                page.file_safe_name)
    323     if self._finder_options.pageset_repeat != 1:
    324       output_file = util.GetSequentialFileName(output_file)
    325     self.browser.profiling_controller.Start(
    326         self._finder_options.profiler, output_file)
    327 
    328   def _StopProfiling(self, results):
    329     if self.browser:
    330       profiler_files = self.browser.profiling_controller.Stop()
    331       for f in profiler_files:
    332         if os.path.isfile(f):
    333           results.AddProfilingFile(self._current_page,
    334                                    file_handle.FromFilePath(f))
    335 
    336 
    337 class SharedMobilePageState(SharedPageState):
    338   _device_type = 'mobile'
    339 
    340 
    341 class SharedDesktopPageState(SharedPageState):
    342   _device_type = 'desktop'
    343 
    344 
    345 class SharedTabletPageState(SharedPageState):
    346   _device_type = 'tablet'
    347 
    348 
    349 class Shared10InchTabletPageState(SharedPageState):
    350   _device_type = 'tablet_10_inch'
    351